func (g *Generator) newSchemaFromStruct(t reflect.Type) *SchemaOrRef {
	if t.Kind() != reflect.Struct {
		return nil
	}
	name := g.typeName(t)
	if _, ok := g.schemaTypes[t]; ok {
		return &SchemaOrRef{Reference: &Reference{
			Ref: componentsSchemaPath + name,
		}}
	}
	schema := &Schema{
		Type:       "object",
		Properties: make(map[string]*SchemaOrRef),
	}
	if name != "" {
		g.schemaTypes[t] = struct{}{}
	}
	schema = g.flattenStructSchema(t, t, schema)
	sor := &SchemaOrRef{Schema: schema}
	if name != "" {
		g.api.Components.Schemas[name] = sor
		return &SchemaOrRef{Reference: &Reference{
			Ref: componentsSchemaPath + name,
		}}
	}
	return sor
}
structSchema returns an OpenAPI schema that describe
the Go struct represented by the type t.
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
}
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
}
func (g *Generator) buildParamsRecursive(op *Operation, t, parent reflect.Type, allowBody bool) error {
	for i := 0; i < t.NumField(); i++ {
		sf := t.Field(i)
		sft := sf.Type
		if sft.Kind() == reflect.Ptr {
			sft = sft.Elem()
		}
		isUnexported := sf.PkgPath != ""
		if sf.Anonymous {
			if isUnexported && sft.Kind() != reflect.Struct {
				continue
			}
			if sft.Kind() == reflect.Struct {
				if sft == parent {
					g.error(&FieldError{
						Message:  "recursive embedding",
						Name:     sf.Name,
						TypeName: g.typeName(parent),
						Type:     parent,
					})
				} else if err := g.buildParamsRecursive(op, sft, parent, allowBody); err != nil {
					return err
				}
			}
			continue
		} else if isUnexported {
			continue
		}
		if err := g.addStructFieldToOperation(op, t, i, allowBody); err != nil {
			return err
		}
	}
	return nil
}
func (g *Generator) paramyByName(p1, p2 *ParameterOrRef) bool {
	return g.resolveParameter(p1).Name < g.resolveParameter(p2).Name
}
func (g *Generator) paramyByLocation(p1, p2 *ParameterOrRef) bool {
	return locationsOrder[g.resolveParameter(p1).In] < locationsOrder[g.resolveParameter(p2).In]
}
func (g *Generator) addStructFieldToOperation(op *Operation, t reflect.Type, idx int, allowBody bool) error {
	sf := t.Field(idx)
	param, err := g.newParameterFromField(idx, t)
	if err != nil {
		return err
	}
	if param != nil {
		for _, p := range op.Parameters {
			if p != nil && (p.Name == param.Name) && (p.In == param.In) {
				g.error(&FieldError{
					Message:           "duplicate parameter",
					Name:              param.Name,
					TypeName:          g.typeName(t),
					Type:              t,
					ParameterLocation: param.In,
				})
				return nil
			}
		}
		op.Parameters = append(op.Parameters, &ParameterOrRef{
			Parameter: param,
		})
	} else {
		if !allowBody {
			return nil
		}
		if sf.Tag.Get("binding") == "-" {
			return nil
		}
		if op.RequestBody == nil {
			op.RequestBody = &RequestBody{
				Content: make(map[string]*MediaType),
			}
		}
		mt := tonic.MediaType()
		if mt == "" {
			mt = anyMediaType
		}
		var schema *Schema
		if _, ok := op.RequestBody.Content[mt]; !ok {
			schema = &Schema{
				Type:       "object",
				Properties: make(map[string]*SchemaOrRef),
			}
			op.RequestBody.Content[mt] = &MediaType{
				Schema: &SchemaOrRef{Schema: schema},
			}
		} else {
			schema = op.RequestBody.Content[mt].Schema.Schema
		}
		fname := fieldNameFromTag(sf, mediaTags[tonic.MediaType()])
		if _, ok := schema.Properties[fname]; ok {
			g.error(&FieldError{
				Message:           "duplicate request body parameter",
				Name:              fname,
				TypeName:          g.typeName(t),
				Type:              t,
				ParameterLocation: "body",
			})
			return nil
		}
		var required bool
		if fname != "" && g.isStructFieldRequired(sf) {
			required = true
			schema.Required = append(schema.Required, fname)
			sort.Strings(schema.Required)
		}
		sfs := g.newSchemaFromStructField(sf, required, fname, t)
		if schema != nil {
			schema.Properties[fname] = sfs
		}
	}
	return nil
}
func (g *Generator) newParameterFromField(idx int, t reflect.Type) (*Parameter, error) {
	field := t.Field(idx)
	location, err := g.paramLocation(field, t)
	if err != nil {
		return nil, err
	}
	if location == "" {
		return nil, nil
	}
	name, err := tonic.ParseTagKey(field.Tag.Get(location))
	if err != nil {
		return nil, err
	}
	required := g.isStructFieldRequired(field)
	if location == g.config.PathLocationTag {
		required = true
	}
	deprecated, _ := strconv.ParseBool(field.Tag.Get(deprecatedTag))
	p := &Parameter{
		Name:        name,
		In:          location,
		Description: field.Tag.Get(descriptionTag),
		Required:    required,
		Deprecated:  deprecated,
		Schema:      g.newSchemaFromStructField(field, required, name, t),
	}
	if field.Type.Kind() == reflect.Bool && location == g.config.QueryLocationTag {
		p.AllowEmptyValue = true
	}
	if location == g.config.QueryLocationTag {
		if field.Type.Kind() == reflect.Slice || field.Type.Kind() == reflect.Array {
			p.Explode = true  
			p.Style = "form"  
			if t := field.Tag.Get(tonic.ExplodeTag); t != "" {
				if explode, err := strconv.ParseBool(t); err == nil && !explode {  
					p.Explode = explode
				}
			}
		}
	}
	return p, nil
}
func (g *Generator) paramLocation(f reflect.StructField, in reflect.Type) (string, error) {
	var c, p int
	has := func(name string, tag reflect.StructTag, i int) {
		if _, ok := tag.Lookup(name); ok {
			c++
			p = i
		}
	}
	var parameterLocations = []string{
		g.config.PathLocationTag,
		g.config.QueryLocationTag,
		g.config.HeaderLocationTag,
	}
	for i, n := range parameterLocations {
		has(n, f.Tag, i)
	}
	if c == 0 {
		return "", nil
	}
	if c > 1 {
		return "", &FieldError{
			Message:  "conflicting parameter location",
			Name:     f.Name,
			TypeName: g.typeName(in),
			Type:     in,
		}
	}
	return parameterLocations[p], nil
}
func (g *Generator) newSchemaFromStructField(sf reflect.StructField, required bool, fname string, parent reflect.Type) *SchemaOrRef {
	sor := g.newSchemaFromType(sf.Type)
	if sor == nil {
		return nil
	}
	schema := g.resolveSchema(sor)
	if schema == nil {
		return sor
	}
	if d := sf.Tag.Get(g.config.DefaultTag); d != "" {
		if required {
			g.error(&FieldError{
				Message:  "field cannot be required and have a default value",
				Name:     fname,
				Type:     sf.Type,
				TypeName: g.typeName(sf.Type),
				Parent:   parent,
			})
		} else {
			if v, err := stringToType(d, sf.Type); err != nil {
				g.error(&FieldError{
					Message:  fmt.Sprintf("default value %s cannot be converted to field type: %s", d, err),
					Name:     fname,
					Type:     sf.Type,
					TypeName: g.typeName(sf.Type),
					Parent:   parent,
				})
			} else {
				schema.Default = v
			}
		}
	}
	enum := g.enumFromStructField(sf, fname, parent)
	if schema.Type == "array" && schema.Items != nil {
		itemsSchema := g.resolveSchema(schema.Items)
		if itemsSchema != nil {
			itemsSchema.Enum = enum
		}
	} else {
		schema.Enum = enum
	}
	if desc, ok := sf.Tag.Lookup(descriptionTag); ok {
		schema.Description = desc
	}
	schema.Deprecated, _ = strconv.ParseBool(sf.Tag.Get(deprecatedTag))
	schema = g.updateSchemaValidation(schema, sf)
	if t, ok := sf.Tag.Lookup(formatTag); ok {
		schema.Format = t
	}
	if e := strings.TrimSpace(sf.Tag.Get("example")); e != "" {
		if parsed, err := parseExampleValue(sf.Type, e); err != nil {
			g.error(&FieldError{
				Message:  fmt.Sprintf("could not parse the example value %q of field %q: %s", e, fname, err),
				Name:     fname,
				Type:     sf.Type,
				TypeName: g.typeName(sf.Type),
				Parent:   parent,
			})
		} else {
			schema.Example = parsed
		}
	}
	return sor
}
func (g *Generator) enumFromStructField(sf reflect.StructField, fname string, parent reflect.Type) []interface{} {
	var enum []interface{}
	etag := sf.Tag.Get(g.config.EnumTag)
	if etag != "" {
		values := strings.Split(etag, ",")
		sftype := sf.Type
		if sftype.Kind() == reflect.Slice || sftype.Kind() == reflect.Array {
			sftype = sftype.Elem()
		}
		for _, val := range values {
			if v, err := stringToType(val, sftype); err != nil {
				g.error(&FieldError{
					Message:  fmt.Sprintf("enum value %s cannot be converted to field type: %s", val, err),
					Name:     fname,
					Type:     sf.Type,
					TypeName: g.typeName(sf.Type),
					Parent:   parent,
				})
			} else {
				enum = append(enum, v)
			}
		}
	}
	return enum
}
func (g *Generator) newSchemaFromType(t reflect.Type) *SchemaOrRef {
	if t == nil {
		return nil
	}
	var nullable bool
	if t.Kind() == reflect.Ptr {
		t = t.Elem()
		nullable = true
	}
	dt := g.datatype(t)
	if dt == TypeUnsupported {
		g.error(&TypeError{
			Message: "unsupported type",
			Type:    t,
		})
		return nil
	}
	if dt == TypeComplex {
		switch t.Kind() {
		case reflect.Slice, reflect.Array, reflect.Map:
			return g.buildSchemaRecursive(t)
		case reflect.Struct:
			return g.newSchemaFromStruct(t)
		}
	}
	if dt == TypeAny {
		return &SchemaOrRef{
			Schema: &Schema{
				Nullable:    true,
				Description: "Value of any type, including null",
			},
		}
	}
	schema := &Schema{
		Type:     dt.Type(),
		Format:   dt.Format(),
		Nullable: nullable,
	}
	return &SchemaOrRef{Schema: schema}
}
func (g *Generator) buildSchemaRecursive(t reflect.Type) *SchemaOrRef {
	schema := &Schema{}
	switch t.Kind() {
	case reflect.Ptr:
		return g.buildSchemaRecursive(t.Elem())
	case reflect.Struct:
		return g.newSchemaFromStruct(t)
	case reflect.Map:
		schema.Type = "object"
		if t.Key().Kind() != reflect.String {
			g.error(&TypeError{
				Message: "encountered type Map with keys of unsupported type",
				Type:    t,
			})
			return nil
		}
		schema.AdditionalProperties = g.buildSchemaRecursive(t.Elem())
	case reflect.Slice, reflect.Array:
		schema.Type = "array"
		if t.Kind() == reflect.Array {
			schema.MinItems = t.Len()
			schema.MaxItems = t.Len()
		}
		schema.Items = g.buildSchemaRecursive(t.Elem())
	default:
		dt := g.datatype(t)
		schema.Type, schema.Format = dt.Type(), dt.Format()
	}
	return &SchemaOrRef{Schema: schema}
}