Definition Builder fails with [][]byte
slow-zhang opened this issue · comments
My struct looks like this:
type email struct {
ID string `json:"id"`
Attachments [][]byte `json:"attachments,omitempty" optional:"true"`
}
My apidocs.json ends up looking something like this:
{
"swagger": "2.0",
...
"definitions": {
"email": {
"properties": {
"id": {
"type": "string"
},
"attachments": {
"type": "array",
"items": {
"$ref": "#/definitions/email.attachments"
}
}
}
}
My [][]byte becomes a reference to a new definition, but that definition is never created.
working on a test to reproduce this https://github.com/emicklei/go-restful-openapi/tree/issue77
i also add a code for that
package main
import (
"log"
"net/http"
restfulspec "github.com/emicklei/go-restful-openapi/v2"
restful "github.com/emicklei/go-restful/v3"
"github.com/go-openapi/spec"
)
type ItemTestResource struct {
// normally one would use DAO (data access object)
users map[string]ItemTest
}
func (u ItemTestResource) WebService() *restful.WebService {
ws := new(restful.WebService)
ws.
Path("/users").
Consumes(restful.MIME_XML, restful.MIME_JSON).
Produces(restful.MIME_JSON, restful.MIME_XML) // you can specify this per route as well
tags := []string{"users"}
ws.Route(ws.GET("/").To(u.findAllItemTests).
// docs
Doc("get all users").
Metadata(restfulspec.KeyOpenAPITags, tags).
Writes([]ItemTest{}).
Returns(200, "OK", []ItemTest{}))
ws.Route(ws.GET("/{user-id}").To(u.findItemTest).
// docs
Doc("get a user").
Param(ws.PathParameter("user-id", "identifier of the user").DataType("integer").DefaultValue("1")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Writes(ItemTest{}). // on the response
Returns(200, "OK", ItemTest{}).
Returns(404, "Not Found", nil))
ws.Route(ws.PUT("/{user-id}").To(u.updateItemTest).
// docs
Doc("update a user").
Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Reads(ItemTest{})) // from the request
ws.Route(ws.PUT("").To(u.createItemTest).
// docs
Doc("create a user").
Metadata(restfulspec.KeyOpenAPITags, tags).
Reads(ItemTest{})) // from the request
ws.Route(ws.DELETE("/{user-id}").To(u.removeItemTest).
// docs
Doc("delete a user").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")))
return ws
}
// GET http://localhost:8080/users
//
func (u ItemTestResource) findAllItemTests(request *restful.Request, response *restful.Response) {
list := []ItemTest{}
for _, each := range u.users {
list = append(list, each)
}
response.WriteEntity(list)
}
// GET http://localhost:8080/users/1
//
func (u ItemTestResource) findItemTest(request *restful.Request, response *restful.Response) {
id := request.PathParameter("user-id")
usr := u.users[id]
if len(usr.ID) == 0 {
response.WriteErrorString(http.StatusNotFound, "ItemTest could not be found.")
} else {
response.WriteEntity(usr)
}
}
// PUT http://localhost:8080/users/1
// <ItemTest><Id>1</Id><Name>Melissa Raspberry</Name></ItemTest>
//
func (u *ItemTestResource) updateItemTest(request *restful.Request, response *restful.Response) {
usr := new(ItemTest)
err := request.ReadEntity(&usr)
if err == nil {
u.users[usr.ID] = *usr
response.WriteEntity(usr)
} else {
response.WriteError(http.StatusInternalServerError, err)
}
}
// PUT http://localhost:8080/users/1
// <ItemTest><Id>1</Id><Name>Melissa</Name></ItemTest>
//
func (u *ItemTestResource) createItemTest(request *restful.Request, response *restful.Response) {
usr := ItemTest{ID: request.PathParameter("user-id")}
err := request.ReadEntity(&usr)
if err == nil {
u.users[usr.ID] = usr
response.WriteHeaderAndEntity(http.StatusCreated, usr)
} else {
response.WriteError(http.StatusInternalServerError, err)
}
}
// DELETE http://localhost:8080/users/1
//
func (u *ItemTestResource) removeItemTest(request *restful.Request, response *restful.Response) {
id := request.PathParameter("user-id")
delete(u.users, id)
}
func main() {
u := ItemTestResource{map[string]ItemTest{}}
restful.DefaultContainer.Add(u.WebService())
config := restfulspec.Config{
WebServices: restful.RegisteredWebServices(), // you control what services are visible
APIPath: "/apidocs.json",
PostBuildSwaggerObjectHandler: enrichSwaggerObject}
restful.DefaultContainer.Add(restfulspec.NewOpenAPIService(config))
// Optionally, you can install the Swagger Service which provides a nice Web UI on your REST API
// You need to download the Swagger HTML5 assets and change the FilePath location in the config below.
// Open http://localhost:8080/apidocs/?url=http://localhost:8080/apidocs.json
http.Handle("/apidocs/", http.StripPrefix("/apidocs/", http.FileServer(http.Dir("/ItemTests/emicklei/Projects/swagger-ui/dist"))))
// Optionally, you may need to enable CORS for the UI to work.
cors := restful.CrossOriginResourceSharing{
AllowedHeaders: []string{"Content-Type", "Accept"},
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE"},
CookiesAllowed: false,
Container: restful.DefaultContainer}
restful.DefaultContainer.Filter(cors.Filter)
log.Printf("Get the API using http://localhost:8080/apidocs.json")
log.Printf("Open Swagger UI using http://localhost:8080/apidocs/?url=http://localhost:8080/apidocs.json")
log.Fatal(http.ListenAndServe(":8080", nil))
}
func enrichSwaggerObject(swo *spec.Swagger) {
swo.Info = &spec.Info{
InfoProps: spec.InfoProps{
Title: "ItemTestService",
Description: "Resource for managing ItemTests",
Version: "1.0.0",
},
}
swo.Tags = []spec.Tag{spec.Tag{TagProps: spec.TagProps{
Name: "users",
Description: "Managing users"}}}
}
type ItemTest struct {
ID string
Type *int64 `protobuf:"varint,1,opt,name=type" json:"type,omitempty"`
NumVal *string `protobuf:"bytes,2,opt,name=numVal" json:"numVal,omitempty"`
BoolVal *bool `protobuf:"varint,3,opt,name=boolVal" json:"boolVal,omitempty"`
StrVal *string `protobuf:"bytes,4,opt,name=strVal" json:"strVal,omitempty"`
MapVal map[string]*ItemValue `protobuf:"bytes,5,rep,name=mapVal" json:"mapVal,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
ListVal []*ItemValue `protobuf:"bytes,6,rep,name=listVal" json:"listVal,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
type ItemValue struct {
Type *int64 `protobuf:"varint,1,opt,name=type" json:"type,omitempty"`
NumVal *string `protobuf:"bytes,2,opt,name=numVal" json:"numVal,omitempty"`
BoolVal *bool `protobuf:"varint,3,opt,name=boolVal" json:"boolVal,omitempty"`
StrVal *string `protobuf:"bytes,4,opt,name=strVal" json:"strVal,omitempty"`
MapVal map[string]string `protobuf:"bytes,5,rep,name=mapVal" json:"mapVal,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
ListVal [][]byte `protobuf:"bytes,6,rep,name=listVal" json:"listVal,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
given your example, do you expect it to be this
"main.ItemValue.listVal": {
"type": "array",
"items": {
"type": "integer"
}
}
this is a [][]byte
in golang, i don't know which will be better for array of interger
or array of string
I also faced this issue with [][]string
:
type Query struct {
Filter map[string]interface{} `json:"filter" optional:"true" description:"..."`
GroupBy [][]string `json:"group_by" optional:"true" description:"...`
Sort []SortField `json:"sort" optional:"true" description:"..."`
}
it generates the swagger for group_by
:
"group_by": {
"type": "array",
"items": {
"$ref": "#/definitions/Query.group_by"
}
}
if I create additional type:
type StringArray []string
type Query struct {
Filter map[string]interface{} `json:"filter" optional:"true" description:"..."`
GroupBy []StringArray `json:"group_by" optional:"true" description:"...`
Sort []SortField `json:"sort" optional:"true" description:"..."`
}
then it generates swagger:
"group_by": {
"type": "array",
"items": {
"$ref": "#/definitions/StringArray"
}
}
but the problem is that it does not create a definition for StringArray
at all in swagger
@emicklei is there some processes for this issue
@slow-zhang yes, but it is slow due to other house-painting work :-) In the meantime, I realised the current PR is more of a workaround. Next time I will work on it, I will generalise it more such that is can handle more cases. (StringArray, ByteByteArray, etc, )
@slow-zhang here is the PR: #78
how can i test it? i rebuild my project with v2.7.0, but it seems failed again for [][]byte
Hi, i rerun the test and get the output as below, is it expected to generate array of interger for [][]byte
test
type Child struct {
ID string `json:"id"`
Attachments [][]byte `json:"attachments,omitempty" optional:"true"`
}
func TestParentChildArray(t *testing.T) {
db := definitionBuilder{Definitions: spec.Definitions{}, Config: Config{}}
db.addModelFrom(Child{})
s := spec.Schema{
SchemaProps: spec.SchemaProps{
Definitions: db.Definitions,
},
}
data, _ := json.MarshalIndent(s, "", " ")
log.Fatalln(string(data))
}
output
{
"definitions": {
"restfulspec.Child": {
"required": [
"id"
],
"properties": {
"attachments": {
"type": "array",
"items": {
"$ref": "#/definitions/restfulspec.Child.attachments"
}
},
"id": {
"type": "string"
}
}
},
"restfulspec.Child.attachments": {
"type": "array",
"items": {
"type": "integer"
}
}
}
}
it is a really good question, in my opinion, the []byte
should be converted to
{
"type": "string",
"format": "binary"
}
if we do
type email struct {
Attachments []byte `json:"attachments,omitempty" optional:"true"`
}
then it is converted to
{
"type": "string",
}
without format
@emicklei what do you think?
Any update on this issue?
I think I got related issue this is when I use struct inside multi dimension slice/array. Here's my example.
type Player struct {
ID string
Name string
}
type Data struct {
Alliances [][]Player
}
And the definition I got is
{
"main.Data": {
"properties": {
"Alliances": {
"type": "array",
"items": {
"$ref": "#/definitions/main.Data.Alliances"
}
}
}
},
"main.Data.Alliances": {
"properties": {
"ID": {
"type": "string"
},
"Name": {
"type": "string"
}
}
}
}
As we can see, the Alliances
is in a 1 dimension array, when what I expect is 2 dimension array.