Skip to content

Commit

Permalink
Feat #36 support map kind
Browse files Browse the repository at this point in the history
  • Loading branch information
gouhuan committed Dec 1, 2023
1 parent 958a34b commit 4ccb43c
Show file tree
Hide file tree
Showing 3 changed files with 177 additions and 4 deletions.
14 changes: 13 additions & 1 deletion api.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,19 @@ type Object struct {

// Property represents the property entity from the swagger definition
type Property struct {
GoType reflect.Type `json:"-"`
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:"-,omitempty"`
Type string `json:"type,omitempty"`
Description string `json:"description,omitempty"`
Enum []string `json:"enum,omitempty"`
Expand Down
150 changes: 147 additions & 3 deletions reflect.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,16 +67,40 @@ 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)

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 {
//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 +130,126 @@ 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()) {
// Example:
// mapType.Elem()==> []*Struct|Struct|*string|string
mapType = mapType.Elem()
}

isPrimitive := isPrimitiveType(mapType.Elem().Name(), mapType.Elem())

if isByteArrayType(mapType.Elem()) {
prop.Type = "string"

Check warning on line 155 in reflect.go

View check run for this annotation

Codecov / codecov/patch

reflect.go#L155

Added line #L155 was not covered by tests
} else {
if isSlice {
prop.Type = types.Array.String()
prop.Items = &Items{}
if isPrimitive {
prop.Items.Type = jsonSchemaType(mapType.Elem().String(), mapType.Elem())
} else {
prop.Items.Ref = makeMapRef(mapType.Elem().String())
}
} else if isPrimitive {
prop.Type = jsonSchemaType(mapType.Elem().String(), mapType.Elem())
} else {
prop.Ref = makeMapRef(mapType.Elem().String())
}

Check warning on line 169 in reflect.go

View check run for this annotation

Codecov / codecov/patch

reflect.go#L165-L169

Added lines #L165 - L169 were not covered by tests
}
}

return &prop
}

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
}

// 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

Check warning on line 202 in reflect.go

View check run for this annotation

Codecov / codecov/patch

reflect.go#L201-L202

Added lines #L201 - L202 were not covered by tests
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

Check warning on line 206 in reflect.go

View check run for this annotation

Codecov / codecov/patch

reflect.go#L205-L206

Added lines #L205 - L206 were not covered by tests
case reflect.String:
return true
}

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

return strings.Contains("time.Time time.Duration json.Number", modelName)
}

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

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

Check warning on line 235 in reflect.go

View check run for this annotation

Codecov / codecov/patch

reflect.go#L234-L235

Added lines #L234 - L235 were not covered by tests

// check if original type is primitive
switch modelKind {
case reflect.Bool:
return "boolean"
case reflect.Float32, reflect.Float64:
return "number"

Check warning on line 242 in reflect.go

View check run for this annotation

Codecov / codecov/patch

reflect.go#L239-L242

Added lines #L239 - L242 were not covered by tests
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return "integer"

Check warning on line 245 in reflect.go

View check run for this annotation

Codecov / codecov/patch

reflect.go#L244-L245

Added lines #L244 - L245 were not covered by tests
case reflect.String:
return "string"
}

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

Check warning on line 250 in reflect.go

View check run for this annotation

Codecov / codecov/patch

reflect.go#L250

Added line #L250 was not covered by tests
}

func buildProperty(t reflect.Type) (map[string]Property, []string) {
properties := make(map[string]Property)
required := make([]string, 0)
Expand Down
17 changes: 17 additions & 0 deletions reflect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,20 @@ func TestMakeSchemaType(t *testing.T) {
objSchema := MakeSchema(struct{}{})
assert.Equal(t, "", objSchema.Type, "expect array type but get %s", objSchema.Type)
}

type PetCat struct {
MapSlicePtr map[string][]*string
MapSlice map[string][]string
MapSliceStructPtr map[string][]*Person
MapSliceStruct map[string][]Person
SliceStructPtr *[]*Person
SliceStruct *[]Person
SliceStringPtr *[]*string
SliceString *[]string
}

func TestDefineWithNewType(t *testing.T) {
v := define(PetCat{})
b, _ := json.Marshal(v)
fmt.Printf("result:%v\n", string(b))
}

0 comments on commit 4ccb43c

Please sign in to comment.