private/apigen: Remove support for anonymous types
I made several commits to add support to the API generator for anonymous types because the example was using them. Later, I was told that we don't need to support them, so the example should have never used anonymous types. Supporting anonymous types has added complexity to the API generator and we are finding bugs in corner cases whose fixes lead to adding more complexity and some fixes doesn't seem to have a neat solution without doing a big refactoring. We decided to reduce the side effects that supporting anonymous type has brought just removing it. This commit remove the support for anonymous types and reflect that in the example. Change-Id: I2f8c87a0db0e229971ab1bef46cca16fee924191
This commit is contained in:
parent
418673f7a2
commit
e67691d51c
@ -102,17 +102,6 @@ func (s *StringBuilder) Writelnf(format string, a ...interface{}) {
|
|||||||
s.WriteString(fmt.Sprintf(format+"\n", a...))
|
s.WriteString(fmt.Sprintf(format+"\n", a...))
|
||||||
}
|
}
|
||||||
|
|
||||||
// typeCustomName is a reflect.Type with a customized type's name.
|
|
||||||
type typeCustomName struct {
|
|
||||||
reflect.Type
|
|
||||||
|
|
||||||
name string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t typeCustomName) Name() string {
|
|
||||||
return t.name
|
|
||||||
}
|
|
||||||
|
|
||||||
// getElementaryType simplifies a Go type.
|
// getElementaryType simplifies a Go type.
|
||||||
func getElementaryType(t reflect.Type) reflect.Type {
|
func getElementaryType(t reflect.Type) reflect.Type {
|
||||||
switch t.Kind() {
|
switch t.Kind() {
|
||||||
@ -132,17 +121,6 @@ func isNillableType(t reflect.Type) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// compoundTypeName create a name composed with base and parts, by joining base as it's and
|
|
||||||
// capitalizing each part. base is not altered.
|
|
||||||
func compoundTypeName(base string, parts ...string) string {
|
|
||||||
titled := make([]string, len(parts))
|
|
||||||
for i := 0; i < len(parts); i++ {
|
|
||||||
titled[i] = capitalize(parts[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
return base + strings.Join(titled, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func capitalize(s string) string {
|
func capitalize(s string) string {
|
||||||
r, size := utf8.DecodeRuneInString(s)
|
r, size := utf8.DecodeRuneInString(s)
|
||||||
if size <= 0 {
|
if size <= 0 {
|
||||||
|
@ -24,6 +24,11 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Endpoint represents endpoint's configuration.
|
// Endpoint represents endpoint's configuration.
|
||||||
|
//
|
||||||
|
// Passing an anonymous type to the fields that define the request or response will make the API
|
||||||
|
// generator to panic. Anonymous types aren't allowed such as named structs that have fields with
|
||||||
|
// direct or indirect of anonymous types, slices or arrays whose direct or indirect elements are of
|
||||||
|
// anonymous types.
|
||||||
type Endpoint struct {
|
type Endpoint struct {
|
||||||
// Name is a free text used to name the endpoint for documentation purpose.
|
// Name is a free text used to name the endpoint for documentation purpose.
|
||||||
// It cannot be empty.
|
// It cannot be empty.
|
||||||
@ -72,24 +77,30 @@ func (e *Endpoint) APIAuth() bool {
|
|||||||
|
|
||||||
// Validate validates the endpoint fields values are correct according to the documented constraints.
|
// Validate validates the endpoint fields values are correct according to the documented constraints.
|
||||||
func (e *Endpoint) Validate() error {
|
func (e *Endpoint) Validate() error {
|
||||||
|
newErr := func(m string, a ...any) error {
|
||||||
|
e := fmt.Sprintf(". Endpoint: %s", e.Name)
|
||||||
|
m += e
|
||||||
|
return errsEndpoint.New(m, a...)
|
||||||
|
}
|
||||||
|
|
||||||
if e.Name == "" {
|
if e.Name == "" {
|
||||||
return errsEndpoint.New("Name cannot be empty")
|
return newErr("Name cannot be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.Description == "" {
|
if e.Description == "" {
|
||||||
return errsEndpoint.New("Description cannot be empty")
|
return newErr("Description cannot be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !goNameRegExp.MatchString(e.GoName) {
|
if !goNameRegExp.MatchString(e.GoName) {
|
||||||
return errsEndpoint.New("GoName doesn't match the regular expression %q", goNameRegExp)
|
return newErr("GoName doesn't match the regular expression %q", goNameRegExp)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !typeScriptNameRegExp.MatchString(e.TypeScriptName) {
|
if !typeScriptNameRegExp.MatchString(e.TypeScriptName) {
|
||||||
return errsEndpoint.New("TypeScriptName doesn't match the regular expression %q", typeScriptNameRegExp)
|
return newErr("TypeScriptName doesn't match the regular expression %q", typeScriptNameRegExp)
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.Request != nil {
|
if e.Request != nil {
|
||||||
switch k := reflect.TypeOf(e.Request).Kind(); k {
|
switch t := reflect.TypeOf(e.Request); t.Kind() {
|
||||||
case reflect.Invalid,
|
case reflect.Invalid,
|
||||||
reflect.Complex64,
|
reflect.Complex64,
|
||||||
reflect.Complex128,
|
reflect.Complex128,
|
||||||
@ -99,12 +110,20 @@ func (e *Endpoint) Validate() error {
|
|||||||
reflect.Map,
|
reflect.Map,
|
||||||
reflect.Pointer,
|
reflect.Pointer,
|
||||||
reflect.UnsafePointer:
|
reflect.UnsafePointer:
|
||||||
return errsEndpoint.New("Request cannot be of a type %q", k)
|
return newErr("Request cannot be of a type %q", t.Kind())
|
||||||
|
case reflect.Array, reflect.Slice:
|
||||||
|
if t.Elem().Name() == "" {
|
||||||
|
return newErr("Request cannot be of %q of anonymous struct elements", t.Kind())
|
||||||
|
}
|
||||||
|
case reflect.Struct:
|
||||||
|
if t.Name() == "" {
|
||||||
|
return newErr("Request cannot be of an anonymous struct")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.Response != nil {
|
if e.Response != nil {
|
||||||
switch k := reflect.TypeOf(e.Response).Kind(); k {
|
switch t := reflect.TypeOf(e.Response); t.Kind() {
|
||||||
case reflect.Invalid,
|
case reflect.Invalid,
|
||||||
reflect.Complex64,
|
reflect.Complex64,
|
||||||
reflect.Complex128,
|
reflect.Complex128,
|
||||||
@ -114,12 +133,20 @@ func (e *Endpoint) Validate() error {
|
|||||||
reflect.Map,
|
reflect.Map,
|
||||||
reflect.Pointer,
|
reflect.Pointer,
|
||||||
reflect.UnsafePointer:
|
reflect.UnsafePointer:
|
||||||
return errsEndpoint.New("Response cannot be of a type %q", k)
|
return newErr("Response cannot be of a type %q", t.Kind())
|
||||||
|
case reflect.Array, reflect.Slice:
|
||||||
|
if t.Elem().Name() == "" {
|
||||||
|
return newErr("Response cannot be of %q of anonymous struct elements", t.Kind())
|
||||||
|
}
|
||||||
|
case reflect.Struct:
|
||||||
|
if t.Name() == "" {
|
||||||
|
return newErr("Response cannot be of an anonymous struct")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.ResponseMock != nil {
|
if e.ResponseMock != nil {
|
||||||
if m, r := reflect.TypeOf(e.ResponseMock), reflect.TypeOf(e.Response); m != r {
|
if m, r := reflect.TypeOf(e.ResponseMock), reflect.TypeOf(e.Response); m != r {
|
||||||
return errsEndpoint.New(
|
return newErr(
|
||||||
"ResponseMock isn't of the same type than Response. Have=%q Want=%q", m, r,
|
"ResponseMock isn't of the same type than Response. Have=%q Want=%q", m, r,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -136,62 +163,6 @@ type fullEndpoint struct {
|
|||||||
Method string
|
Method string
|
||||||
}
|
}
|
||||||
|
|
||||||
// requestType guarantees to return a named Go type associated to the Endpoint.Request field.
|
|
||||||
// g is used to avoid clashes with types defined in different groups that are different, but with
|
|
||||||
// the same name. It cannot be nil.
|
|
||||||
func (fe fullEndpoint) requestType(g *EndpointGroup) reflect.Type {
|
|
||||||
t := reflect.TypeOf(fe.Request)
|
|
||||||
if t.Name() != "" {
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
switch k := t.Kind(); k {
|
|
||||||
case reflect.Array, reflect.Slice:
|
|
||||||
if t.Elem().Name() == "" {
|
|
||||||
t = typeCustomName{Type: t, name: compoundTypeName(capitalize(g.Prefix), fe.TypeScriptName, "Request")}
|
|
||||||
}
|
|
||||||
case reflect.Struct:
|
|
||||||
t = typeCustomName{Type: t, name: compoundTypeName(capitalize(g.Prefix), fe.TypeScriptName, "Request")}
|
|
||||||
default:
|
|
||||||
panic(
|
|
||||||
fmt.Sprintf(
|
|
||||||
"BUG: Unsupported Request type. Endpoint.Method=%q, Endpoint.Path=%q, found type=%q",
|
|
||||||
fe.Method, fe.Path, k,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
// responseType guarantees to return a named Go type associated to the Endpoint.Response field.
|
|
||||||
// g is used to avoid clashes with types defined in different groups that are different, but with
|
|
||||||
// the same name. It cannot be nil.
|
|
||||||
func (fe fullEndpoint) responseType(g *EndpointGroup) reflect.Type {
|
|
||||||
t := reflect.TypeOf(fe.Response)
|
|
||||||
if t.Name() != "" {
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
switch k := t.Kind(); k {
|
|
||||||
case reflect.Array, reflect.Slice:
|
|
||||||
if t.Elem().Name() == "" {
|
|
||||||
t = typeCustomName{Type: t, name: compoundTypeName(capitalize(g.Prefix), fe.TypeScriptName, "Response")}
|
|
||||||
}
|
|
||||||
case reflect.Struct:
|
|
||||||
t = typeCustomName{Type: t, name: compoundTypeName(capitalize(g.Prefix), fe.TypeScriptName, "Response")}
|
|
||||||
default:
|
|
||||||
panic(
|
|
||||||
fmt.Sprintf(
|
|
||||||
"BUG: Unsupported Response type. Endpoint.Method=%q, Endpoint.Path=%q, found type=%q",
|
|
||||||
fe.Method, fe.Path, k,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
// EndpointGroup represents endpoints group.
|
// EndpointGroup represents endpoints group.
|
||||||
// You should always create a group using API.Group because it validates the field values to
|
// You should always create a group using API.Group because it validates the field values to
|
||||||
// guarantee correct code generation.
|
// guarantee correct code generation.
|
||||||
@ -295,7 +266,10 @@ type Param struct {
|
|||||||
Type reflect.Type
|
Type reflect.Type
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewParam constructor which creates new Param entity by given name and type.
|
// NewParam constructor which creates new Param entity by given name and type through instance.
|
||||||
|
//
|
||||||
|
// instance can only be a unsigned integer (of any size), string, uuid.UUID or time.Time, otherwise
|
||||||
|
// it panics.
|
||||||
func NewParam(name string, instance interface{}) Param {
|
func NewParam(name string, instance interface{}) Param {
|
||||||
switch t := reflect.TypeOf(instance); t {
|
switch t := reflect.TypeOf(instance); t {
|
||||||
case reflect.TypeOf(uuid.UUID{}), reflect.TypeOf(time.Time{}):
|
case reflect.TypeOf(uuid.UUID{}), reflect.TypeOf(time.Time{}):
|
||||||
@ -320,16 +294,3 @@ func NewParam(name string, instance interface{}) Param {
|
|||||||
Type: reflect.TypeOf(instance),
|
Type: reflect.TypeOf(instance),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// namedType guarantees to return a named Go type. where defines where the param is defined (e.g.
|
|
||||||
// path, query, etc.).
|
|
||||||
func (p Param) namedType(ep Endpoint, where string) reflect.Type {
|
|
||||||
if p.Type.Name() == "" {
|
|
||||||
return typeCustomName{
|
|
||||||
Type: p.Type,
|
|
||||||
name: compoundTypeName(ep.TypeScriptName, where, "param", p.Name),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return p.Type
|
|
||||||
}
|
|
||||||
|
@ -25,40 +25,16 @@ var ErrDocsAPI = errs.Class("example docs api")
|
|||||||
var ErrUsersAPI = errs.Class("example users api")
|
var ErrUsersAPI = errs.Class("example users api")
|
||||||
|
|
||||||
type DocumentsService interface {
|
type DocumentsService interface {
|
||||||
Get(ctx context.Context) ([]struct {
|
Get(ctx context.Context) ([]myapi.Document, api.HTTPError)
|
||||||
ID uuid.UUID "json:\"id\""
|
|
||||||
Path string "json:\"path\""
|
|
||||||
Date time.Time "json:\"date\""
|
|
||||||
Metadata myapi.Metadata "json:\"metadata\""
|
|
||||||
LastRetrievals []struct {
|
|
||||||
User string "json:\"user\""
|
|
||||||
When time.Time "json:\"when\""
|
|
||||||
} "json:\"last_retrievals\""
|
|
||||||
}, api.HTTPError)
|
|
||||||
GetOne(ctx context.Context, path string) (*myapi.Document, api.HTTPError)
|
GetOne(ctx context.Context, path string) (*myapi.Document, api.HTTPError)
|
||||||
GetTag(ctx context.Context, path, tagName string) (*[2]string, api.HTTPError)
|
GetTag(ctx context.Context, path, tagName string) (*[2]string, api.HTTPError)
|
||||||
GetVersions(ctx context.Context, path string) ([]myapi.Version, api.HTTPError)
|
GetVersions(ctx context.Context, path string) ([]myapi.Version, api.HTTPError)
|
||||||
UpdateContent(ctx context.Context, path string, id uuid.UUID, date time.Time, request struct {
|
UpdateContent(ctx context.Context, path string, id uuid.UUID, date time.Time, request myapi.NewDocument) (*myapi.Document, api.HTTPError)
|
||||||
Content string "json:\"content\""
|
|
||||||
}) (*struct {
|
|
||||||
ID uuid.UUID "json:\"id\""
|
|
||||||
Date time.Time "json:\"date\""
|
|
||||||
PathParam string "json:\"pathParam\""
|
|
||||||
Body string "json:\"body\""
|
|
||||||
}, api.HTTPError)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type UsersService interface {
|
type UsersService interface {
|
||||||
Get(ctx context.Context) ([]struct {
|
Get(ctx context.Context) ([]myapi.User, api.HTTPError)
|
||||||
Name string "json:\"name\""
|
Create(ctx context.Context, request []myapi.User) api.HTTPError
|
||||||
Surname string "json:\"surname\""
|
|
||||||
Email string "json:\"email\""
|
|
||||||
}, api.HTTPError)
|
|
||||||
Create(ctx context.Context, request []struct {
|
|
||||||
Name string "json:\"name\""
|
|
||||||
Surname string "json:\"surname\""
|
|
||||||
Email string "json:\"email\""
|
|
||||||
}) api.HTTPError
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DocumentsHandler is an api handler that implements all Documents API endpoints functionality.
|
// DocumentsHandler is an api handler that implements all Documents API endpoints functionality.
|
||||||
@ -275,9 +251,7 @@ func (h *DocumentsHandler) handleUpdateContent(w http.ResponseWriter, r *http.Re
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
payload := struct {
|
payload := myapi.NewDocument{}
|
||||||
Content string "json:\"content\""
|
|
||||||
}{}
|
|
||||||
if err = json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
if err = json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
||||||
api.ServeError(h.log, w, http.StatusBadRequest, err)
|
api.ServeError(h.log, w, http.StatusBadRequest, err)
|
||||||
return
|
return
|
||||||
@ -335,11 +309,7 @@ func (h *UsersHandler) handleCreate(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
payload := []struct {
|
payload := []myapi.User{}
|
||||||
Name string "json:\"name\""
|
|
||||||
Surname string "json:\"surname\""
|
|
||||||
Email string "json:\"email\""
|
|
||||||
}{}
|
|
||||||
if err = json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
if err = json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
||||||
api.ServeError(h.log, w, http.StatusBadRequest, err)
|
api.ServeError(h.log, w, http.StatusBadRequest, err)
|
||||||
return
|
return
|
||||||
|
@ -28,8 +28,14 @@ Get the paths to all the documents under the specified paths
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
id: string // UUID formatted as `00000000-0000-0000-0000-000000000000`
|
id: string // UUID formatted as `00000000-0000-0000-0000-000000000000`
|
||||||
path: string
|
|
||||||
date: string // Date timestamp formatted as `2006-01-02T15:00:00Z`
|
date: string // Date timestamp formatted as `2006-01-02T15:00:00Z`
|
||||||
|
pathParam: string
|
||||||
|
body: string
|
||||||
|
version: {
|
||||||
|
date: string // Date timestamp formatted as `2006-01-02T15:00:00Z`
|
||||||
|
number: number
|
||||||
|
}
|
||||||
|
|
||||||
metadata: {
|
metadata: {
|
||||||
owner: string
|
owner: string
|
||||||
tags: [
|
tags: [
|
||||||
@ -38,14 +44,6 @@ unknown
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
last_retrievals: [
|
|
||||||
{
|
|
||||||
user: string
|
|
||||||
when: string // Date timestamp formatted as `2006-01-02T15:00:00Z`
|
|
||||||
}
|
|
||||||
|
|
||||||
]
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
@ -77,6 +75,14 @@ Get the document in the specified path
|
|||||||
number: number
|
number: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
metadata: {
|
||||||
|
owner: string
|
||||||
|
tags: [
|
||||||
|
unknown
|
||||||
|
]
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -161,6 +167,19 @@ Update the content of the document with the specified path and ID if the last up
|
|||||||
date: string // Date timestamp formatted as `2006-01-02T15:00:00Z`
|
date: string // Date timestamp formatted as `2006-01-02T15:00:00Z`
|
||||||
pathParam: string
|
pathParam: string
|
||||||
body: string
|
body: string
|
||||||
|
version: {
|
||||||
|
date: string // Date timestamp formatted as `2006-01-02T15:00:00Z`
|
||||||
|
number: number
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata: {
|
||||||
|
owner: string
|
||||||
|
tags: [
|
||||||
|
unknown
|
||||||
|
]
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -31,39 +31,14 @@ func main() {
|
|||||||
Description: "Get the paths to all the documents under the specified paths",
|
Description: "Get the paths to all the documents under the specified paths",
|
||||||
GoName: "Get",
|
GoName: "Get",
|
||||||
TypeScriptName: "get",
|
TypeScriptName: "get",
|
||||||
Response: []struct {
|
Response: []myapi.Document{},
|
||||||
ID uuid.UUID `json:"id"`
|
ResponseMock: []myapi.Document{{
|
||||||
Path string `json:"path"`
|
|
||||||
Date time.Time `json:"date"`
|
|
||||||
Metadata myapi.Metadata `json:"metadata"`
|
|
||||||
LastRetrievals []struct {
|
|
||||||
User string `json:"user"`
|
|
||||||
When time.Time `json:"when"`
|
|
||||||
} `json:"last_retrievals"`
|
|
||||||
}{},
|
|
||||||
ResponseMock: []struct {
|
|
||||||
ID uuid.UUID `json:"id"`
|
|
||||||
Path string `json:"path"`
|
|
||||||
Date time.Time `json:"date"`
|
|
||||||
Metadata myapi.Metadata `json:"metadata"`
|
|
||||||
LastRetrievals []struct {
|
|
||||||
User string `json:"user"`
|
|
||||||
When time.Time `json:"when"`
|
|
||||||
} `json:"last_retrievals"`
|
|
||||||
}{{
|
|
||||||
ID: uuid.UUID{},
|
ID: uuid.UUID{},
|
||||||
Path: "/workspace/notes.md",
|
PathParam: "/workspace/notes.md",
|
||||||
Metadata: myapi.Metadata{
|
Metadata: myapi.Metadata{
|
||||||
Owner: "Storj",
|
Owner: "Storj",
|
||||||
Tags: [][2]string{{"category", "general"}},
|
Tags: [][2]string{{"category", "general"}},
|
||||||
},
|
},
|
||||||
LastRetrievals: []struct {
|
|
||||||
User string `json:"user"`
|
|
||||||
When time.Time `json:"when"`
|
|
||||||
}{{
|
|
||||||
User: "Storj",
|
|
||||||
When: now.Add(-time.Hour),
|
|
||||||
}},
|
|
||||||
}},
|
}},
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -121,15 +96,8 @@ func main() {
|
|||||||
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",
|
||||||
GoName: "UpdateContent",
|
GoName: "UpdateContent",
|
||||||
TypeScriptName: "updateContent",
|
TypeScriptName: "updateContent",
|
||||||
Response: struct {
|
Response: myapi.Document{},
|
||||||
ID uuid.UUID `json:"id"`
|
Request: myapi.NewDocument{},
|
||||||
Date time.Time `json:"date"`
|
|
||||||
PathParam string `json:"pathParam"`
|
|
||||||
Body string `json:"body"`
|
|
||||||
}{},
|
|
||||||
Request: struct {
|
|
||||||
Content string `json:"content"`
|
|
||||||
}{},
|
|
||||||
QueryParams: []apigen.Param{
|
QueryParams: []apigen.Param{
|
||||||
apigen.NewParam("id", uuid.UUID{}),
|
apigen.NewParam("id", uuid.UUID{}),
|
||||||
apigen.NewParam("date", time.Time{}),
|
apigen.NewParam("date", time.Time{}),
|
||||||
@ -137,12 +105,7 @@ func main() {
|
|||||||
PathParams: []apigen.Param{
|
PathParams: []apigen.Param{
|
||||||
apigen.NewParam("path", ""),
|
apigen.NewParam("path", ""),
|
||||||
},
|
},
|
||||||
ResponseMock: struct {
|
ResponseMock: myapi.Document{
|
||||||
ID uuid.UUID `json:"id"`
|
|
||||||
Date time.Time `json:"date"`
|
|
||||||
PathParam string `json:"pathParam"`
|
|
||||||
Body string `json:"body"`
|
|
||||||
}{
|
|
||||||
ID: uuid.UUID{},
|
ID: uuid.UUID{},
|
||||||
Date: now,
|
Date: now,
|
||||||
PathParam: "ID",
|
PathParam: "ID",
|
||||||
@ -157,16 +120,8 @@ func main() {
|
|||||||
Description: "Get the list of registered users",
|
Description: "Get the list of registered users",
|
||||||
GoName: "Get",
|
GoName: "Get",
|
||||||
TypeScriptName: "get",
|
TypeScriptName: "get",
|
||||||
Response: []struct {
|
Response: []myapi.User{},
|
||||||
Name string `json:"name"`
|
ResponseMock: []myapi.User{
|
||||||
Surname string `json:"surname"`
|
|
||||||
Email string `json:"email"`
|
|
||||||
}{},
|
|
||||||
ResponseMock: []struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Surname string `json:"surname"`
|
|
||||||
Email string `json:"email"`
|
|
||||||
}{
|
|
||||||
{Name: "Storj", Surname: "Labs", Email: "storj@storj.test"},
|
{Name: "Storj", Surname: "Labs", Email: "storj@storj.test"},
|
||||||
{Name: "Test1", Surname: "Testing", Email: "test1@example.test"},
|
{Name: "Test1", Surname: "Testing", Email: "test1@example.test"},
|
||||||
{Name: "Test2", Surname: "Testing", Email: "test2@example.test"},
|
{Name: "Test2", Surname: "Testing", Email: "test2@example.test"},
|
||||||
@ -178,11 +133,7 @@ func main() {
|
|||||||
Description: "Create a user",
|
Description: "Create a user",
|
||||||
GoName: "Create",
|
GoName: "Create",
|
||||||
TypeScriptName: "create",
|
TypeScriptName: "create",
|
||||||
Request: []struct {
|
Request: []myapi.User{},
|
||||||
Name string `json:"name"`
|
|
||||||
Surname string `json:"surname"`
|
|
||||||
Email string `json:"email"`
|
|
||||||
}{},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
a.MustWriteGo("api.gen.go")
|
a.MustWriteGo("api.gen.go")
|
||||||
|
@ -16,6 +16,7 @@ type Document struct {
|
|||||||
PathParam string `json:"pathParam"`
|
PathParam string `json:"pathParam"`
|
||||||
Body string `json:"body"`
|
Body string `json:"body"`
|
||||||
Version Version `json:"version"`
|
Version Version `json:"version"`
|
||||||
|
Metadata Metadata `json:"metadata"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Version is document version.
|
// Version is document version.
|
||||||
@ -29,3 +30,15 @@ type Metadata struct {
|
|||||||
Owner string `json:"owner"`
|
Owner string `json:"owner"`
|
||||||
Tags [][2]string `json:"tags"`
|
Tags [][2]string `json:"tags"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewDocument contains the content the data to create a new document.
|
||||||
|
type NewDocument struct {
|
||||||
|
Content string `json:"content"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// User contains information of a user.
|
||||||
|
type User struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Surname string `json:"surname"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
}
|
||||||
|
@ -31,22 +31,6 @@ import (
|
|||||||
type (
|
type (
|
||||||
auth struct{}
|
auth struct{}
|
||||||
service struct{}
|
service struct{}
|
||||||
responseGet = struct {
|
|
||||||
ID uuid.UUID `json:"id"`
|
|
||||||
Path string `json:"path"`
|
|
||||||
Date time.Time `json:"date"`
|
|
||||||
Metadata myapi.Metadata `json:"metadata"`
|
|
||||||
LastRetrievals []struct {
|
|
||||||
User string `json:"user"`
|
|
||||||
When time.Time `json:"when"`
|
|
||||||
} `json:"last_retrievals"`
|
|
||||||
}
|
|
||||||
responseUpdateContent = struct {
|
|
||||||
ID uuid.UUID `json:"id"`
|
|
||||||
Date time.Time `json:"date"`
|
|
||||||
PathParam string `json:"pathParam"`
|
|
||||||
Body string `json:"body"`
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a auth) IsAuthenticated(ctx context.Context, r *http.Request, isCookieAuth, isKeyAuth bool) (context.Context, error) {
|
func (a auth) IsAuthenticated(ctx context.Context, r *http.Request, isCookieAuth, isKeyAuth bool) (context.Context, error) {
|
||||||
@ -57,8 +41,8 @@ func (a auth) RemoveAuthCookie(w http.ResponseWriter) {}
|
|||||||
|
|
||||||
func (s service) Get(
|
func (s service) Get(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
) ([]responseGet, api.HTTPError) {
|
) ([]myapi.Document, api.HTTPError) {
|
||||||
return []responseGet{}, api.HTTPError{}
|
return []myapi.Document{}, api.HTTPError{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s service) GetOne(
|
func (s service) GetOne(
|
||||||
@ -88,11 +72,9 @@ func (s service) UpdateContent(
|
|||||||
pathParam string,
|
pathParam string,
|
||||||
id uuid.UUID,
|
id uuid.UUID,
|
||||||
date time.Time,
|
date time.Time,
|
||||||
body struct {
|
body myapi.NewDocument,
|
||||||
Content string `json:"content"`
|
) (*myapi.Document, api.HTTPError) {
|
||||||
},
|
return &myapi.Document{
|
||||||
) (*responseUpdateContent, api.HTTPError) {
|
|
||||||
return &responseUpdateContent{
|
|
||||||
ID: id,
|
ID: id,
|
||||||
Date: date,
|
Date: date,
|
||||||
PathParam: pathParam,
|
PathParam: pathParam,
|
||||||
@ -100,7 +82,9 @@ func (s service) UpdateContent(
|
|||||||
}, api.HTTPError{}
|
}, api.HTTPError{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func send(ctx context.Context, method string, url string, body interface{}) ([]byte, error) {
|
func send(ctx context.Context, t *testing.T, method string, url string, body interface{}) ([]byte, error) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
var bodyReader io.Reader = http.NoBody
|
var bodyReader io.Reader = http.NoBody
|
||||||
if body != nil {
|
if body != nil {
|
||||||
bodyJSON, err := json.Marshal(body)
|
bodyJSON, err := json.Marshal(body)
|
||||||
@ -120,6 +104,10 @@ func send(ctx context.Context, method string, url string, body interface{}) ([]b
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c := resp.StatusCode; c != http.StatusOK {
|
||||||
|
t.Fatalf("unexpected status code. Want=%d, Got=%d", http.StatusOK, c)
|
||||||
|
}
|
||||||
|
|
||||||
respBody, err := io.ReadAll(resp.Body)
|
respBody, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -145,14 +133,14 @@ func TestAPIServer(t *testing.T) {
|
|||||||
id, err := uuid.New()
|
id, err := uuid.New()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
expected := responseUpdateContent{
|
expected := myapi.Document{
|
||||||
ID: id,
|
ID: id,
|
||||||
Date: time.Now(),
|
Date: time.Now(),
|
||||||
PathParam: "foo",
|
PathParam: "foo",
|
||||||
Body: "bar",
|
Body: "bar",
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := send(ctx, http.MethodPost,
|
resp, err := send(ctx, t, http.MethodPost,
|
||||||
fmt.Sprintf("%s/api/v0/docs/%s?id=%s&date=%s",
|
fmt.Sprintf("%s/api/v0/docs/%s?id=%s&date=%s",
|
||||||
server.URL,
|
server.URL,
|
||||||
expected.PathParam,
|
expected.PathParam,
|
||||||
@ -162,14 +150,16 @@ func TestAPIServer(t *testing.T) {
|
|||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var actual map[string]string
|
fmt.Println(string(resp))
|
||||||
|
|
||||||
|
var actual map[string]any
|
||||||
require.NoError(t, json.Unmarshal(resp, &actual))
|
require.NoError(t, json.Unmarshal(resp, &actual))
|
||||||
|
|
||||||
for _, key := range []string{"id", "date", "pathParam", "body"} {
|
for _, key := range []string{"id", "date", "pathParam", "body"} {
|
||||||
require.Contains(t, actual, key)
|
require.Contains(t, actual, key)
|
||||||
}
|
}
|
||||||
require.Equal(t, expected.ID.String(), actual["id"])
|
require.Equal(t, expected.ID.String(), actual["id"].(string))
|
||||||
require.Equal(t, expected.Date.Format(apigen.DateFormat), actual["date"])
|
require.Equal(t, expected.Date.Format(apigen.DateFormat), actual["date"].(string))
|
||||||
require.Equal(t, expected.PathParam, actual["pathParam"])
|
require.Equal(t, expected.PathParam, actual["pathParam"].(string))
|
||||||
require.Equal(t, expected.Body, actual["body"])
|
require.Equal(t, expected.Body, actual["body"].(string))
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ package apigen
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/zeebo/errs"
|
"github.com/zeebo/errs"
|
||||||
@ -79,14 +80,14 @@ func (f *tsGenFile) registerTypes() {
|
|||||||
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 {
|
||||||
f.types.Register(method.requestType(group))
|
f.types.Register(reflect.TypeOf(method.Request))
|
||||||
}
|
}
|
||||||
if method.Response != nil {
|
if method.Response != nil {
|
||||||
f.types.Register(method.responseType(group))
|
f.types.Register(reflect.TypeOf(method.Response))
|
||||||
}
|
}
|
||||||
if len(method.QueryParams) > 0 {
|
if len(method.QueryParams) > 0 {
|
||||||
for _, p := range method.QueryParams {
|
for _, p := range method.QueryParams {
|
||||||
t := getElementaryType(p.namedType(method.Endpoint, "query"))
|
t := getElementaryType(p.Type)
|
||||||
f.types.Register(t)
|
f.types.Register(t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -106,8 +107,7 @@ func (f *tsGenFile) createAPIClient(group *EndpointGroup) {
|
|||||||
returnStmt := "return"
|
returnStmt := "return"
|
||||||
returnType := "void"
|
returnType := "void"
|
||||||
if method.Response != nil {
|
if method.Response != nil {
|
||||||
respType := method.responseType(group)
|
returnType = TypescriptTypeName(reflect.TypeOf(method.Response))
|
||||||
returnType = TypescriptTypeName(respType)
|
|
||||||
returnStmt += fmt.Sprintf(" response.json().then((body) => body as %s)", returnType)
|
returnStmt += fmt.Sprintf(" response.json().then((body) => body as %s)", returnType)
|
||||||
}
|
}
|
||||||
returnStmt += ";"
|
returnStmt += ";"
|
||||||
@ -149,16 +149,16 @@ func (f *tsGenFile) getArgsAndPath(method *fullEndpoint, group *EndpointGroup) (
|
|||||||
path = "${this.ROOT_PATH}" + path
|
path = "${this.ROOT_PATH}" + path
|
||||||
|
|
||||||
if method.Request != nil {
|
if method.Request != nil {
|
||||||
funcArgs += fmt.Sprintf("request: %s, ", TypescriptTypeName(method.requestType(group)))
|
funcArgs += fmt.Sprintf("request: %s, ", TypescriptTypeName(reflect.TypeOf(method.Request)))
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, p := range method.PathParams {
|
for _, p := range method.PathParams {
|
||||||
funcArgs += fmt.Sprintf("%s: %s, ", p.Name, TypescriptTypeName(p.namedType(method.Endpoint, "path")))
|
funcArgs += fmt.Sprintf("%s: %s, ", p.Name, TypescriptTypeName(p.Type))
|
||||||
path += fmt.Sprintf("/${%s}", p.Name)
|
path += fmt.Sprintf("/${%s}", p.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, p := range method.QueryParams {
|
for _, p := range method.QueryParams {
|
||||||
funcArgs += fmt.Sprintf("%s: %s, ", p.Name, TypescriptTypeName(p.namedType(method.Endpoint, "query")))
|
funcArgs += fmt.Sprintf("%s: %s, ", p.Name, TypescriptTypeName(p.Type))
|
||||||
}
|
}
|
||||||
|
|
||||||
path = strings.ReplaceAll(path, "//", "/")
|
path = strings.ReplaceAll(path, "//", "/")
|
||||||
|
@ -6,6 +6,7 @@ package apigen
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/zeebo/errs"
|
"github.com/zeebo/errs"
|
||||||
@ -98,8 +99,7 @@ func (f *tsGenMockFile) createAPIClient(group *EndpointGroup) {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
respType := method.responseType(group)
|
returnType = TypescriptTypeName(reflect.TypeOf(method.Response))
|
||||||
returnType = TypescriptTypeName(respType)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
f.pf("\tpublic async %s(%s): Promise<%s> {", method.TypeScriptName, funcArgs, returnType)
|
f.pf("\tpublic async %s(%s): Promise<%s> {", method.TypeScriptName, funcArgs, returnType)
|
||||||
|
@ -52,73 +52,35 @@ func (types *Types) Register(t reflect.Type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// All returns a map containing every top-level and their dependency types with their associated name.
|
// All returns a map containing every top-level and their dependency types with their associated name.
|
||||||
//
|
|
||||||
// TODO: see how to have a better implementation for adding to seen, uniqueNames, and all.
|
|
||||||
func (types *Types) All() map[reflect.Type]string {
|
func (types *Types) All() map[reflect.Type]string {
|
||||||
all := map[reflect.Type]string{}
|
all := map[reflect.Type]string{}
|
||||||
uniqueNames := map[string]struct{}{}
|
|
||||||
|
|
||||||
var walk func(t reflect.Type, alternateTypeName string)
|
var walk func(t reflect.Type)
|
||||||
walk = func(t reflect.Type, altTypeName string) {
|
walk = func(t reflect.Type) {
|
||||||
if _, ok := all[t]; ok {
|
if _, ok := all[t]; ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.Name() != "" {
|
|
||||||
// Type isn't seen it but it has the same name than a seen it one.
|
|
||||||
// This cannot be because we would generate more than one TypeScript type with the same name.
|
|
||||||
if _, ok := uniqueNames[t.Name()]; ok {
|
|
||||||
panic(fmt.Sprintf("Found different types with the same name (%s)", t.Name()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if n, ok := commonClasses[t]; ok {
|
if n, ok := commonClasses[t]; ok {
|
||||||
all[t] = n
|
all[t] = n
|
||||||
uniqueNames[n] = struct{}{}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch k := t.Kind(); k {
|
switch k := t.Kind(); k {
|
||||||
case reflect.Ptr:
|
case reflect.Ptr:
|
||||||
walk(t.Elem(), altTypeName)
|
walk(t.Elem())
|
||||||
case reflect.Array, reflect.Slice:
|
case reflect.Array, reflect.Slice:
|
||||||
// If element type has a TypeScript name then an array of the element type will be defined
|
walk(t.Elem())
|
||||||
// otherwise we have to create a compound type.
|
|
||||||
elemTypeName := t.Elem().Name()
|
|
||||||
if tsen := TypescriptTypeName(t.Elem()); tsen == "" {
|
|
||||||
if altTypeName == "" {
|
|
||||||
panic(
|
|
||||||
fmt.Sprintf(
|
|
||||||
"BUG: found a %q with elements of an anonymous type and without an alternative name. Found type=%q",
|
|
||||||
t.Kind(),
|
|
||||||
t,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
all[t] = altTypeName
|
|
||||||
uniqueNames[altTypeName] = struct{}{}
|
|
||||||
elemTypeName = compoundTypeName(altTypeName, "item")
|
|
||||||
}
|
|
||||||
walk(t.Elem(), elemTypeName)
|
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
n := t.Name()
|
if t.Name() == "" {
|
||||||
if n == "" {
|
panic(fmt.Sprintf("BUG: found an anonymous 'struct'. Found type=%q", t))
|
||||||
if altTypeName == "" {
|
|
||||||
panic(
|
|
||||||
fmt.Sprintf(
|
|
||||||
"BUG: found an anonymous 'struct' and without an alternative name; an alternative name is required. Found type=%q",
|
|
||||||
t,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
n = altTypeName
|
all[t] = t.Name()
|
||||||
}
|
|
||||||
|
|
||||||
all[t] = n
|
|
||||||
uniqueNames[n] = struct{}{}
|
|
||||||
|
|
||||||
for i := 0; i < t.NumField(); i++ {
|
for i := 0; i < t.NumField(); i++ {
|
||||||
field := t.Field(i)
|
field := t.Field(i)
|
||||||
walk(field.Type, compoundTypeName(altTypeName, field.Name))
|
walk(field.Type)
|
||||||
}
|
}
|
||||||
case reflect.Bool,
|
case reflect.Bool,
|
||||||
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||||
@ -126,14 +88,13 @@ func (types *Types) All() map[reflect.Type]string {
|
|||||||
reflect.Float32, reflect.Float64,
|
reflect.Float32, reflect.Float64,
|
||||||
reflect.String:
|
reflect.String:
|
||||||
all[t] = t.Name()
|
all[t] = t.Name()
|
||||||
uniqueNames[t.Name()] = struct{}{}
|
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("type %q is not supported", t.Kind().String()))
|
panic(fmt.Sprintf("type %q is not supported", t.Kind().String()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for t := range types.top {
|
for t := range types.top {
|
||||||
walk(t, t.Name())
|
walk(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
return all
|
return all
|
||||||
@ -186,42 +147,11 @@ func (types *Types) GenerateTypescriptDefinitions() string {
|
|||||||
isOptional = "?"
|
isOptional = "?"
|
||||||
}
|
}
|
||||||
|
|
||||||
if field.Type.Name() != "" {
|
|
||||||
pf("\t%s%s: %s;", jsonField, isOptional, TypescriptTypeName(field.Type))
|
pf("\t%s%s: %s;", jsonField, isOptional, TypescriptTypeName(field.Type))
|
||||||
} else {
|
|
||||||
typeName := allTypes[field.Type]
|
|
||||||
pf("\t%s%s: %s;", jsonField, isOptional, TypescriptTypeName(typeCustomName{Type: field.Type, name: typeName}))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
allArraySlices := filter(namedTypes, func(t typeAndName) bool {
|
|
||||||
if _, ok := commonClasses[t.Type]; ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
switch t.Type.Kind() {
|
|
||||||
case reflect.Array, reflect.Slice:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
for _, t := range allArraySlices {
|
|
||||||
elemTypeName, ok := allTypes[t.Type.Elem()]
|
|
||||||
if !ok {
|
|
||||||
panic("BUG: the element types of an Slice or Array isn't in the all types map")
|
|
||||||
}
|
|
||||||
pf(
|
|
||||||
"\nexport type %s = Array<%s>",
|
|
||||||
TypescriptTypeName(
|
|
||||||
typeCustomName{Type: t.Type, name: t.Name}),
|
|
||||||
TypescriptTypeName(typeCustomName{Type: t.Type.Elem(), name: elemTypeName}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return out.String()
|
return out.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -279,6 +209,9 @@ func TypescriptTypeName(t reflect.Type) string {
|
|||||||
case reflect.Bool:
|
case reflect.Bool:
|
||||||
return "boolean"
|
return "boolean"
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
|
if t.Name() == "" {
|
||||||
|
panic(fmt.Sprintf(`anonymous struct aren't accepted because their type doesn't have a name. Type="%+v"`, t))
|
||||||
|
}
|
||||||
return capitalize(t.Name())
|
return capitalize(t.Name())
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf(`unhandled type. Type="%+v"`, t))
|
panic(fmt.Sprintf(`unhandled type. Type="%+v"`, t))
|
||||||
|
@ -51,80 +51,29 @@ func TestTypes(t *testing.T) {
|
|||||||
require.Subset(t, allTypes, typesList, "all types contains at least the registered ones")
|
require.Subset(t, allTypes, typesList, "all types contains at least the registered ones")
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("All nested structs and slices", func(t *testing.T) {
|
t.Run("Anonymous types panics", func(t *testing.T) {
|
||||||
type Item struct{}
|
type Address struct {
|
||||||
types := NewTypes()
|
|
||||||
types.Register(
|
|
||||||
typeCustomName{
|
|
||||||
Type: reflect.TypeOf(struct {
|
|
||||||
Name string
|
|
||||||
Addresses []struct {
|
|
||||||
Address string
|
Address string
|
||||||
PO string
|
PO string
|
||||||
}
|
}
|
||||||
Job struct {
|
type Job struct {
|
||||||
Company string
|
Company string
|
||||||
Position string
|
Position string
|
||||||
StartingYear uint
|
StartingYear uint
|
||||||
|
ContractClauses []struct { // This is what it makes Types.All to panic
|
||||||
|
ClauseID uint
|
||||||
|
CauseDesc string
|
||||||
}
|
}
|
||||||
Documents []struct {
|
|
||||||
Path string
|
|
||||||
Content string
|
|
||||||
Valoration testTypesValoration
|
|
||||||
}
|
|
||||||
Items []Item
|
|
||||||
}{}),
|
|
||||||
name: "Response",
|
|
||||||
})
|
|
||||||
|
|
||||||
allTypes := types.All()
|
|
||||||
require.Len(t, allTypes, 10, "total number of types")
|
|
||||||
|
|
||||||
typesNames := []string{}
|
|
||||||
for _, name := range allTypes {
|
|
||||||
typesNames = append(typesNames, name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
require.ElementsMatch(t, []string{
|
type Citizen struct {
|
||||||
"string", "uint",
|
|
||||||
"Response",
|
|
||||||
"ResponseAddresses", "ResponseAddressesItem",
|
|
||||||
"ResponseJob",
|
|
||||||
"ResponseDocuments", "ResponseDocumentsItem", "testTypesValoration",
|
|
||||||
"Item",
|
|
||||||
}, typesNames)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("All panic types without unique names", func(t *testing.T) {
|
|
||||||
types := NewTypes()
|
|
||||||
types.Register(typeCustomName{
|
|
||||||
Type: reflect.TypeOf(struct {
|
|
||||||
Name string
|
Name string
|
||||||
Addresses []struct {
|
Addresses []Address
|
||||||
Address string
|
Job Job
|
||||||
PO string
|
|
||||||
}
|
}
|
||||||
Job struct {
|
|
||||||
Company string
|
|
||||||
Position string
|
|
||||||
StartingYear uint
|
|
||||||
}
|
|
||||||
Documents []struct {
|
|
||||||
Path string
|
|
||||||
Content string
|
|
||||||
Valoration testTypesValoration
|
|
||||||
}
|
|
||||||
}{}),
|
|
||||||
name: "Response",
|
|
||||||
})
|
|
||||||
|
|
||||||
types.Register(typeCustomName{
|
|
||||||
Type: reflect.TypeOf(struct {
|
|
||||||
Reference string
|
|
||||||
}{}),
|
|
||||||
name: "Response",
|
|
||||||
})
|
|
||||||
|
|
||||||
|
types := NewTypes()
|
||||||
|
types.Register(reflect.TypeOf(Citizen{}))
|
||||||
require.Panics(t, func() {
|
require.Panics(t, func() {
|
||||||
types.All()
|
types.All()
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user