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
Merged
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,29 @@ Golang 1.16+
go get -u github.com/zc2638/[email protected]
```

## Use note:
The time.time type uses jsonTag to parse it into a string type
```go
type XXX struct{
Now time.Time `json:",string"`
}
```

The compatibility is resolved as follows:
```go
type xxx 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
}

```

**Tip:** As of `v1.2.0`, lower versions are no longer compatible. In order to be compatible with most web frameworks,
the overall architecture has been greatly changed.

Expand Down
23 changes: 23 additions & 0 deletions README_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,29 @@ Golang 1.16+
go get -u github.com/zc2638/[email protected]
```

## 使用注意:
time.time类型使用jsonTag来将其解析为string类型
```go
type XXX struct{
Now time.Time `json:",string"`
}
```

兼容的解析如下:
```go
type xxx 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
}

```

**Tip:** 从 `v1.2.0` 开始,低版本不再兼容。为了兼容大部分的web框架,整体架构做了很大的改动。

## 默认 Swagger UI 服务器
Expand Down
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
173 changes: 168 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,41 @@
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

//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,145 @@
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"

Check warning on line 159 in reflect.go

View check run for this annotation

Codecov / codecov/patch

reflect.go#L159

Added line #L159 was not covered by tests
} 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())

Check warning on line 173 in reflect.go

View check run for this annotation

Codecov / codecov/patch

reflect.go#L172-L173

Added lines #L172 - L173 were not covered by tests
} 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
}

// isByteArrayType
// Check if the data is of slice or array type
// while simultaneously verifying if the element type is of byte type(in go defined "type byte=uint8").
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

Check warning on line 226 in reflect.go

View check run for this annotation

Codecov / codecov/patch

reflect.go#L225-L226

Added lines #L225 - L226 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 230 in reflect.go

View check run for this annotation

Codecov / codecov/patch

reflect.go#L229-L230

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

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

_, ok := types.SchemaMap[modelName]
return ok
}

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()
}

if mapped, ok := types.SchemaMap[modelName]; ok {
return mapped
}

Check warning on line 254 in reflect.go

View check run for this annotation

Codecov / codecov/patch

reflect.go#L253-L254

Added lines #L253 - L254 were not covered by tests

// check if original type is primitive
switch modelKind {
case reflect.Bool:
return types.Boolean.String()
case reflect.Float32, reflect.Float64:
return types.Number.String()

Check warning on line 261 in reflect.go

View check run for this annotation

Codecov / codecov/patch

reflect.go#L258-L261

Added lines #L258 - L261 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 types.Integer.String()

Check warning on line 264 in reflect.go

View check run for this annotation

Codecov / codecov/patch

reflect.go#L263-L264

Added lines #L263 - L264 were not covered by tests
case reflect.String:
return types.String.String()
}

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

Check warning on line 269 in reflect.go

View check run for this annotation

Codecov / codecov/patch

reflect.go#L269

Added line #L269 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
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["github.com_zc2638_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
Loading