func (g *Generator) setOperationParams(op *Operation, t, parent reflect.Type, allowBody bool, path string) error {
	if t.Kind() != reflect.Struct {
		return errors.New("input type is not a struct")
	}
	if err := g.buildParamsRecursive(op, t, parent, allowBody); err != nil {
		return err
	}
	if op.RequestBody != nil {
		mt := tonic.MediaType()
		if mt == "" {
			mt = anyMediaType
		}
		sch := op.RequestBody.Content[mt].Schema
		if sch != nil {
			name := strings.Title(op.ID) + "Input"
			g.api.Components.Schemas[name] = sch
			op.RequestBody.Content[mt].Schema = &SchemaOrRef{Reference: &Reference{
				Ref: componentsSchemaPath + name,
			}}
		}
	}
	matches := paramsInPathRe.FindAllStringSubmatch(path, -1)
	var pathParams []string
	for _, m := range matches {
		pathParams = append(pathParams, m[1])
	}
	for _, pp := range pathParams {
		has := false
		for _, param := range op.Parameters {
			if param.In == "path" && param.Name == pp {
				has = true
				break
			}
		}
		if !has {
			return fmt.Errorf("semantic error for path %s: declared path parameter %s needs to be defined at operation level", path, pp)
		}
	}
	if g.sortParams {
		paramsOrderedBy(
			g.paramyByLocation,
			g.paramyByName,
		).Sort(op.Parameters)
	}
	return nil
}
setOperationParams adds the fields of the struct type t
to the given operation.
package openapi
import (
	"errors"
	"fmt"
	"net/http"
	"reflect"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"github.com/gofrs/uuid"
	"github.com/loopfz/gadgeto/tonic"
)
const (
	version              = "3.0.1"
	anyMediaType         = "*/*"
	formatTag            = "format"
	deprecatedTag        = "deprecated"
	descriptionTag       = "description"
	componentsSchemaPath = "#/components/schemas/"
)
var (
	paramsInPathRe = regexp.MustCompile(`\{(.*?)\}`)
	ginPathParamRe = regexp.MustCompile(`\/:([^\/]*)`)
)
var mediaTags = map[string]string{
	"application/json": "json",
	"application/xml":  "xml",
}
type Generator struct {
	api           *OpenAPI
	config        *SpecGenConfig
	schemaTypes   map[reflect.Type]struct{}
	typeNames     map[reflect.Type]string
	dataTypes     map[reflect.Type]*OverridedDataType
	operationsIDS map[string]struct{}
	errors        []error
	fullNames     bool
	sortParams    bool
	sortTags      bool
}
func NewGenerator(conf *SpecGenConfig) (*Generator, error) {
	if conf == nil {
		return nil, errors.New("missing config")
	}
	components := &Components{
		Schemas:    make(map[string]*SchemaOrRef),
		Responses:  make(map[string]*ResponseOrRef),
		Parameters: make(map[string]*ParameterOrRef),
		Headers:    make(map[string]*HeaderOrRef),
	}
	return &Generator{
		config: conf,
		api: &OpenAPI{
			OpenAPI:    version,
			Info:       &Info{},
			Paths:      make(Paths),
			Components: components,
		},
		schemaTypes:   make(map[reflect.Type]struct{}),
		typeNames:     make(map[reflect.Type]string),
		dataTypes:     make(map[reflect.Type]*OverridedDataType),
		operationsIDS: make(map[string]struct{}),
		fullNames:     true,
		sortParams:    true,
		sortTags:      true,
	}, nil
}
type SpecGenConfig struct {
	ValidatorTag      string
	PathLocationTag   string
	QueryLocationTag  string
	HeaderLocationTag string
	EnumTag           string
	DefaultTag        string
}
func (g *Generator) SetInfo(info *Info) {
	g.api.Info = info
}
func (g *Generator) SetServers(servers []*Server) {
	g.api.Servers = servers
}
func (g *Generator) API() *OpenAPI {
	cpy := *g.api
	return &cpy
}
func (g *Generator) Errors() []error {
	return g.errors
}
func (g *Generator) UseFullSchemaNames(b bool) {
	g.fullNames = b
}
func (g *Generator) SetSortParams(b bool) {
	g.sortParams = b
}
func (g *Generator) SetSortTags(b bool) {
	g.sortTags = b
}
func (g *Generator) OverrideTypeName(t reflect.Type, name string) error {
	if name == "" {
		return errors.New("type name is empty")
	}
	if t.Kind() == reflect.Ptr {
		t = t.Elem()
	}
	if _, ok := g.typeNames[t]; ok {
		return errors.New("type name already overrided")
	}
	g.typeNames[t] = name
	return nil
}
func (g *Generator) OverrideDataType(t reflect.Type, typ, format string) error {
	if typ == "" {
		return errors.New("type is mandatory")
	}
	if t.Kind() == reflect.Ptr {
		t = t.Elem()
	}
	if _, ok := g.dataTypes[t]; ok {
		return errors.New("data type already overrided")
	}
	g.dataTypes[t] = &OverridedDataType{
		format: format,
		typ:    typ,
	}
	return nil
}
func (g *Generator) datatype(t reflect.Type) DataType {
	if dt, ok := g.dataTypes[t]; ok {
		return dt
	}
	return DataTypeFromType(t)
}
func (g *Generator) AddTag(name, desc string) {
	if name == "" {
		return
	}
	for _, tag := range g.api.Tags {
		if tag != nil {
			if tag.Name == name {
				tag.Description = desc
				return
			}
		}
	}
	g.api.Tags = append(g.api.Tags, &Tag{
		Name:        name,
		Description: desc,
	})
	if g.sortTags {
		sort.SliceStable(g.api.Tags, func(i, j int) bool {
			if g.api.Tags[i] != nil && g.api.Tags[j] != nil {
				return g.api.Tags[i].Name < g.api.Tags[j].Name
			}
			return false
		})
	}
}
func (g *Generator) AddOperation(path, method, tag string, in, out reflect.Type, info *OperationInfo) (*Operation, error) {
	op := &Operation{
		ID: uuid.Must(uuid.NewV4()).String(),
	}
	path = rewritePath(path)
	if info != nil {
		if _, ok := g.operationsIDS[info.ID]; ok {
			return nil, fmt.Errorf("ID %s is already used by another operation", info.ID)
		}
		g.operationsIDS[info.ID] = struct{}{}
	}
	item, ok := g.api.Paths[path]
	if !ok {
		item = new(PathItem)
		g.api.Paths[path] = item
	}
	if info != nil {
		op.ID = info.ID
		op.Summary = info.Summary
		op.Description = info.Description
		op.Deprecated = info.Deprecated
		op.Responses = make(Responses)
		op.XCodeSamples = info.XCodeSamples
	}
	if tag != "" {
		op.Tags = append(op.Tags, tag)
	}
	allowBody := method != http.MethodGet &&
		method != http.MethodHead &&
		method != http.MethodDelete
	if in != nil {
		if in.Kind() == reflect.Ptr {
			in = in.Elem()
		}
		if in.Kind() != reflect.Struct {
			return nil, errors.New("input type is not a struct")
		}
		if err := g.setOperationParams(op, in, in, allowBody, path); err != nil {
			return nil, err
		}
	}
	if err := g.setOperationResponse(op, out, strconv.Itoa(info.StatusCode), tonic.MediaType(), info.StatusDescription, info.Headers, nil, nil); err != nil {
		return nil, err
	}
	for _, resp := range info.Responses {
		if resp != nil {
			if err := g.setOperationResponse(op,
				reflect.TypeOf(resp.Model),
				resp.Code,
				tonic.MediaType(),
				resp.Description,
				resp.Headers,
				resp.Example,
				resp.Examples,
			); err != nil {
				return nil, err
			}
		}
	}
	setOperationBymethod(item, op, method)
	return op, nil
}
func rewritePath(path string) string {
	return ginPathParamRe.ReplaceAllString(path, "/{$1}")
}
func setOperationBymethod(item *PathItem, op *Operation, method string) {
	switch method {
	case "GET":
		item.GET = op
	case "PUT":
		item.PUT = op
	case "POST":
		item.POST = op
	case "PATCH":
		item.PATCH = op
	case "HEAD":
		item.HEAD = op
	case "OPTIONS":
		item.OPTIONS = op
	case "TRACE":
		item.TRACE = op
	case "DELETE":
		item.DELETE = op
	}
}
func isResponseCodeRange(code string) bool {
	if len(code) != 3 {
		return false
	}
	pre := code[0]
	if pre < 49 || pre > 53 {
		return false
	}
	if code[1] != 'X' || code[2] != 'X' {
		return false
	}
	return true
}
func (g *Generator) setOperationResponse(op *Operation, t reflect.Type, code, mt, desc string, headers []*ResponseHeader, example interface{}, examples map[string]interface{}) error {
	if _, ok := op.Responses[code]; ok {
		return fmt.Errorf("response with code %s already exists", code)
	}
	if example != nil && examples != nil {
		return fmt.Errorf("'example' and 'examples' are mutually exclusive")
	}
	if code != "default" {
		if !isResponseCodeRange(code) {  
			ci, err := strconv.Atoi(code)
			if err != nil {
				return fmt.Errorf("invalid response code: %s", err)
			}
			if ci < 100 || ci > 599 {
				return fmt.Errorf("response code out of range: %s", code)
			}
			desc = http.StatusText(ci)
		}
	}
	r := &Response{
		Description: desc,
		Content:     make(map[string]*MediaTypeOrRef),
		Headers:     make(map[string]*HeaderOrRef),
	}
	var castedExamples map[string]*ExampleOrRef
	if examples != nil {
		castedExamples = make(map[string]*ExampleOrRef)
		for name, val := range examples {
			castedExamples[name] = &ExampleOrRef{Example: &Example{Value: val}}
		}
	}
	schema := g.newSchemaFromType(t)
	if schema != nil || example != nil || castedExamples != nil {
		r.Content[mt] = &MediaTypeOrRef{MediaType: &MediaType{
			Schema:   schema,
			Example:  example,
			Examples: castedExamples,
		}}
	}
	for _, h := range headers {
		if h != nil {
			var sor *SchemaOrRef
			if h.Model == nil {
				sor = &SchemaOrRef{Schema: &Schema{Type: "string"}}
			} else {
				sor = g.newSchemaFromType(reflect.TypeOf(h.Model))
			}
			r.Headers[h.Name] = &HeaderOrRef{Header: &Header{
				Description: h.Description,
				Schema:      sor,
			}}
		}
	}
	op.Responses[code] = &ResponseOrRef{Response: r}
	return nil
}