Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Definition Builder fails with [][]byte #77

Open
slow-zhang opened this issue Nov 8, 2021 · 14 comments
Open

Definition Builder fails with [][]byte #77

slow-zhang opened this issue Nov 8, 2021 · 14 comments

Comments

@slow-zhang
Copy link
Contributor

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.

@emicklei
Copy link
Owner

emicklei commented Nov 8, 2021

working on a test to reproduce this https://github.com/emicklei/go-restful-openapi/tree/issue77

@slow-zhang
Copy link
Contributor Author

thanks for helping, here is the real Definition:
image
image
image

@slow-zhang
Copy link
Contributor Author

slow-zhang commented Nov 9, 2021

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:"-"`
}

@emicklei
Copy link
Owner

given your example, do you expect it to be this

   "main.ItemValue.listVal": {
      "type": "array",
      "items": {
        "type": "integer"
      }
    }

@slow-zhang
Copy link
Contributor Author

this is a [][]byte in golang, i don't know which will be better for array of interger or array of string

@SVilgelm
Copy link
Contributor

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

@slow-zhang
Copy link
Contributor Author

@emicklei is there some processes for this issue

@emicklei
Copy link
Owner

@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, )

@SVilgelm
Copy link
Contributor

@slow-zhang here is the PR: #78

@slow-zhang
Copy link
Contributor Author

how can i test it? i rebuild my project with v2.7.0, but it seems failed again for [][]byte

@slow-zhang
Copy link
Contributor Author

PTAL @emicklei @SVilgelm

@slow-zhang
Copy link
Contributor Author

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"
      }
    }
  }
}

@SVilgelm
Copy link
Contributor

SVilgelm commented Jan 5, 2022

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?

@dialytica
Copy link
Contributor

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants