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

Feat support map #40

Merged
merged 11 commits into from
Apr 2, 2024
12 changes: 12 additions & 0 deletions api.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,18 @@ type Object struct {

// Property represents the property entity from the swagger definition
type Property struct {
GoType reflect.Type `json:"-"`
Type string `json:"type,omitempty"`
Description string `json:"description,omitempty"`
Enum []string `json:"enum,omitempty"`
Format string `json:"format,omitempty"`
Ref string `json:"$ref,omitempty"`
Example string `json:"example,omitempty"`
Items *Items `json:"items,omitempty"`
AddPropertie *AdditionalProperties `json:"additionalProperties,omitempty"`
}

type AdditionalProperties struct {
GoType reflect.Type `json:"-"`
Type string `json:"type,omitempty"`
Description string `json:"description,omitempty"`
Expand Down
176 changes: 171 additions & 5 deletions reflect.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,9 @@
package swag

import (
"github.com/zc2638/swag/types"
"reflect"
"strings"

"github.com/zc2638/swag/types"
)

func inspect(t reflect.Type, jsonTag string) Property {
Expand Down Expand Up @@ -67,16 +66,42 @@ func inspect(t reflect.Type, jsonTag string) Property {
name := makeName(p.GoType)
p.Ref = makeRef(name)

case reflect.Map:
p.Type = "object"
p.AddPropertie = buildMapType(p.GoType)
// get the go reflect type of the map value
p.GoType = p.AddPropertie.GoType

case reflect.Slice:
p.Type = types.Array.String()
p.Items = &Items{}

p.GoType = t.Elem() // dereference the slice
// dereference the slice,get the element object of the slice
// Example:
// t ==> *[]*Struct|Struct|*string|string
// p.GoType.Kind() ==> []*Struct|Struct|*string|string
if p.GoType.Kind() == reflect.Slice {
HugoWw marked this conversation as resolved.
Show resolved Hide resolved
//p.GoType ==> *Struct|Struct|*string|string
p.GoType = p.GoType.Elem()
}

switch p.GoType.Kind() {
case reflect.Ptr:
//p.GoType ==> Struct|string
p.GoType = p.GoType.Elem()
name := makeName(p.GoType)
p.Items.Ref = makeRef(name)

// determine the type of element object in the slice
isPrimitive := isPrimitiveType(p.GoType.Name(), p.GoType)

if isPrimitive {
// golang built-in primitive type
kind_type := jsonSchemaType(p.GoType.String(), p.GoType)
p.Items.Type = kind_type
} else {
// Struct types
name := makeName(p.GoType)
p.Items.Ref = makeRef(name)
}

case reflect.Struct:
name := makeName(p.GoType)
Expand Down Expand Up @@ -106,6 +131,147 @@ func inspect(t reflect.Type, jsonTag string) Property {
return p
}

// buildMapType build map type swag info
func buildMapType(mapType reflect.Type) *AdditionalProperties {

prop := AdditionalProperties{}

// get the element object of the map
// Example:
// mapType ==> map[string][]*Struct|Struct|*string|string
// or mapTYpe ==> map[string]*Struct|Struct|*string|string
// mapType.Elem().Kind() ==> []*Struct|Struct|*string|string
// or mapType.Elem().Kind() ==> *Struct|Struct|*string|string
if mapType.Elem().Kind().String() != "interface" {
isSlice := isSliceOrArryType(mapType.Elem().Kind())
if isSlice || isByteArrayType(mapType.Elem()) {
// if map value is slice
// Example:
// mapType.Elem()==> []*Struct|Struct|*string|string
mapType = mapType.Elem()
}

// if map value is struct or built-in primitive type
// Example:
// mapType.Elem()==> *Struct|Struct|*string|string
isPrimitive := isPrimitiveType(mapType.Elem().Name(), mapType.Elem())

if isByteArrayType(mapType.Elem()) {
prop.Type = "string"
} else {
zc2638 marked this conversation as resolved.
Show resolved Hide resolved
if isSlice {
prop.Type = types.Array.String()
prop.Items = &Items{}
if isPrimitive {
prop.Items.Type = jsonSchemaType(mapType.Elem().String(), mapType.Elem())
prop.GoType = getGoType(mapType.Elem())
} else {
prop.Items.Ref = makeMapRef(mapType.Elem().String())
prop.GoType = getGoType(mapType.Elem())
}
} else if isPrimitive {
prop.Type = jsonSchemaType(mapType.Elem().String(), mapType.Elem())
prop.GoType = getGoType(mapType.Elem())
} else {
prop.Ref = makeMapRef(mapType.Elem().String())
prop.GoType = getGoType(mapType.Elem())
}
}
}

return &prop
}

func getGoType(t reflect.Type) reflect.Type {

var goType reflect.Type

if t.Kind() == reflect.Ptr {
goType = t.Elem()
} else {
goType = t
}

return goType
}

func makeMapRef(typeName string) string {
type_name := strings.Trim(typeName, "*")
return makeRef(type_name)
}

func isSliceOrArryType(t reflect.Kind) bool {
return t == reflect.Slice || t == reflect.Array
}

func isByteArrayType(t reflect.Type) bool {
return (t.Kind() == reflect.Slice || t.Kind() == reflect.Array) &&
t.Elem().Kind() == reflect.Uint8
}
zc2638 marked this conversation as resolved.
Show resolved Hide resolved

// isPrimitiveType Whether it is a built-in primitive type
func isPrimitiveType(modelName string, modelType reflect.Type) bool {
var modelKind reflect.Kind

if modelType.Kind() == reflect.Ptr {
modelKind = modelType.Elem().Kind()
} else {
modelKind = modelType.Kind()
}

switch modelKind {
case reflect.Bool:
return true
case reflect.Float32, reflect.Float64,
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return true
case reflect.String:
return true
}

if len(modelName) == 0 {
return false
}

return strings.Contains("time.Time time.Duration json.Number", modelName)
HugoWw marked this conversation as resolved.
Show resolved Hide resolved
}

func jsonSchemaType(modelName string, modelType reflect.Type) string {
var modelKind reflect.Kind

if modelType.Kind() == reflect.Ptr {
modelKind = modelType.Elem().Kind()
} else {
modelKind = modelType.Kind()
}

schemaMap := map[string]string{
"time.Time": "string",
"time.Duration": "integer",
"json.Number": "number",
}
zc2638 marked this conversation as resolved.
Show resolved Hide resolved

if mapped, ok := schemaMap[modelName]; ok {
return mapped
}

// check if original type is primitive
switch modelKind {
case reflect.Bool:
return "boolean"
case reflect.Float32, reflect.Float64:
return "number"
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return "integer"
case reflect.String:
return "string"
}
HugoWw marked this conversation as resolved.
Show resolved Hide resolved

return modelName // use as is (custom or struct)
}

func buildProperty(t reflect.Type) (map[string]Property, []string) {
properties := make(map[string]Property)
required := make([]string, 0)
Expand Down
20 changes: 19 additions & 1 deletion reflect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,24 @@ type Pet struct {
Bool bool
Enum string `json:"enum" enum:"a,b,c" example:"b"`
Anonymous
MapSlicePtr map[string][]*string
MapSlice map[string][]string
MapSliceStructPtr map[string][]*Person
MapSliceStruct map[string][]Person
SliceStructPtr *[]*Person
SliceStruct *[]Person
SliceStringPtr *[]*string
SliceString *[]string
MapNestOptions *MapObj `json:"map_nest_options,omitempty"`
}

type MapObj struct {
RuleOptions map[string]*MapOption `json:"rule_options"`
}

type MapOption struct {
Name string `json:"name"`
SubOptions map[string]*MapOption `json:"sub_options,omitempty"`
}

type Empty struct {
Expand All @@ -61,7 +79,7 @@ func TestDefine(t *testing.T) {
obj, ok := v["swag.Pet"]
assert.True(t, ok)
assert.False(t, obj.IsArray)
assert.Equal(t, 17, len(obj.Properties))
assert.Equal(t, 26, len(obj.Properties))

content := make(map[string]Object)
data, err := os.ReadFile("testdata/pet.json")
Expand Down
Loading