summaryrefslogtreecommitdiff
path: root/vendor/github.com/emicklei/go-restful-swagger12/model_builder.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/emicklei/go-restful-swagger12/model_builder.go')
-rw-r--r--vendor/github.com/emicklei/go-restful-swagger12/model_builder.go467
1 files changed, 467 insertions, 0 deletions
diff --git a/vendor/github.com/emicklei/go-restful-swagger12/model_builder.go b/vendor/github.com/emicklei/go-restful-swagger12/model_builder.go
new file mode 100644
index 000000000..d40786f25
--- /dev/null
+++ b/vendor/github.com/emicklei/go-restful-swagger12/model_builder.go
@@ -0,0 +1,467 @@
+package swagger
+
+import (
+ "encoding/json"
+ "reflect"
+ "strings"
+)
+
+// ModelBuildable is used for extending Structs that need more control over
+// how the Model appears in the Swagger api declaration.
+type ModelBuildable interface {
+ PostBuildModel(m *Model) *Model
+}
+
+type modelBuilder struct {
+ Models *ModelList
+ Config *Config
+}
+
+type documentable interface {
+ SwaggerDoc() map[string]string
+}
+
+// Check if this structure has a method with signature func (<theModel>) SwaggerDoc() map[string]string
+// If it exists, retrive the documentation and overwrite all struct tag descriptions
+func getDocFromMethodSwaggerDoc2(model reflect.Type) map[string]string {
+ if docable, ok := reflect.New(model).Elem().Interface().(documentable); ok {
+ return docable.SwaggerDoc()
+ }
+ return make(map[string]string)
+}
+
+// addModelFrom creates and adds a Model to the builder and detects and calls
+// the post build hook for customizations
+func (b modelBuilder) addModelFrom(sample interface{}) {
+ if modelOrNil := b.addModel(reflect.TypeOf(sample), ""); modelOrNil != nil {
+ // allow customizations
+ if buildable, ok := sample.(ModelBuildable); ok {
+ modelOrNil = buildable.PostBuildModel(modelOrNil)
+ b.Models.Put(modelOrNil.Id, *modelOrNil)
+ }
+ }
+}
+
+func (b modelBuilder) addModel(st reflect.Type, nameOverride string) *Model {
+ // Turn pointers into simpler types so further checks are
+ // correct.
+ if st.Kind() == reflect.Ptr {
+ st = st.Elem()
+ }
+
+ modelName := b.keyFrom(st)
+ if nameOverride != "" {
+ modelName = nameOverride
+ }
+ // no models needed for primitive types
+ if b.isPrimitiveType(modelName) {
+ return nil
+ }
+ // golang encoding/json packages says array and slice values encode as
+ // JSON arrays, except that []byte encodes as a base64-encoded string.
+ // If we see a []byte here, treat it at as a primitive type (string)
+ // and deal with it in buildArrayTypeProperty.
+ if (st.Kind() == reflect.Slice || st.Kind() == reflect.Array) &&
+ st.Elem().Kind() == reflect.Uint8 {
+ return nil
+ }
+ // see if we already have visited this model
+ if _, ok := b.Models.At(modelName); ok {
+ return nil
+ }
+ sm := Model{
+ Id: modelName,
+ Required: []string{},
+ Properties: ModelPropertyList{}}
+
+ // reference the model before further initializing (enables recursive structs)
+ b.Models.Put(modelName, sm)
+
+ // check for slice or array
+ if st.Kind() == reflect.Slice || st.Kind() == reflect.Array {
+ b.addModel(st.Elem(), "")
+ return &sm
+ }
+ // check for structure or primitive type
+ if st.Kind() != reflect.Struct {
+ return &sm
+ }
+
+ fullDoc := getDocFromMethodSwaggerDoc2(st)
+ modelDescriptions := []string{}
+
+ for i := 0; i < st.NumField(); i++ {
+ field := st.Field(i)
+ jsonName, modelDescription, prop := b.buildProperty(field, &sm, modelName)
+ if len(modelDescription) > 0 {
+ modelDescriptions = append(modelDescriptions, modelDescription)
+ }
+
+ // add if not omitted
+ if len(jsonName) != 0 {
+ // update description
+ if fieldDoc, ok := fullDoc[jsonName]; ok {
+ prop.Description = fieldDoc
+ }
+ // update Required
+ if b.isPropertyRequired(field) {
+ sm.Required = append(sm.Required, jsonName)
+ }
+ sm.Properties.Put(jsonName, prop)
+ }
+ }
+
+ // We always overwrite documentation if SwaggerDoc method exists
+ // "" is special for documenting the struct itself
+ if modelDoc, ok := fullDoc[""]; ok {
+ sm.Description = modelDoc
+ } else if len(modelDescriptions) != 0 {
+ sm.Description = strings.Join(modelDescriptions, "\n")
+ }
+
+ // update model builder with completed model
+ b.Models.Put(modelName, sm)
+
+ return &sm
+}
+
+func (b modelBuilder) isPropertyRequired(field reflect.StructField) bool {
+ required := true
+ if jsonTag := field.Tag.Get("json"); jsonTag != "" {
+ s := strings.Split(jsonTag, ",")
+ if len(s) > 1 && s[1] == "omitempty" {
+ return false
+ }
+ }
+ return required
+}
+
+func (b modelBuilder) buildProperty(field reflect.StructField, model *Model, modelName string) (jsonName, modelDescription string, prop ModelProperty) {
+ jsonName = b.jsonNameOfField(field)
+ if len(jsonName) == 0 {
+ // empty name signals skip property
+ return "", "", prop
+ }
+
+ if field.Name == "XMLName" && field.Type.String() == "xml.Name" {
+ // property is metadata for the xml.Name attribute, can be skipped
+ return "", "", prop
+ }
+
+ if tag := field.Tag.Get("modelDescription"); tag != "" {
+ modelDescription = tag
+ }
+
+ prop.setPropertyMetadata(field)
+ if prop.Type != nil {
+ return jsonName, modelDescription, prop
+ }
+ fieldType := field.Type
+
+ // check if type is doing its own marshalling
+ marshalerType := reflect.TypeOf((*json.Marshaler)(nil)).Elem()
+ if fieldType.Implements(marshalerType) {
+ var pType = "string"
+ if prop.Type == nil {
+ prop.Type = &pType
+ }
+ if prop.Format == "" {
+ prop.Format = b.jsonSchemaFormat(b.keyFrom(fieldType))
+ }
+ return jsonName, modelDescription, prop
+ }
+
+ // check if annotation says it is a string
+ if jsonTag := field.Tag.Get("json"); jsonTag != "" {
+ s := strings.Split(jsonTag, ",")
+ if len(s) > 1 && s[1] == "string" {
+ stringt := "string"
+ prop.Type = &stringt
+ return jsonName, modelDescription, prop
+ }
+ }
+
+ fieldKind := fieldType.Kind()
+ switch {
+ case fieldKind == reflect.Struct:
+ jsonName, prop := b.buildStructTypeProperty(field, jsonName, model)
+ return jsonName, modelDescription, prop
+ case fieldKind == reflect.Slice || fieldKind == reflect.Array:
+ jsonName, prop := b.buildArrayTypeProperty(field, jsonName, modelName)
+ return jsonName, modelDescription, prop
+ case fieldKind == reflect.Ptr:
+ jsonName, prop := b.buildPointerTypeProperty(field, jsonName, modelName)
+ return jsonName, modelDescription, prop
+ case fieldKind == reflect.String:
+ stringt := "string"
+ prop.Type = &stringt
+ return jsonName, modelDescription, prop
+ case fieldKind == reflect.Map:
+ // if it's a map, it's unstructured, and swagger 1.2 can't handle it
+ objectType := "object"
+ prop.Type = &objectType
+ return jsonName, modelDescription, prop
+ }
+
+ fieldTypeName := b.keyFrom(fieldType)
+ if b.isPrimitiveType(fieldTypeName) {
+ mapped := b.jsonSchemaType(fieldTypeName)
+ prop.Type = &mapped
+ prop.Format = b.jsonSchemaFormat(fieldTypeName)
+ return jsonName, modelDescription, prop
+ }
+ modelType := b.keyFrom(fieldType)
+ prop.Ref = &modelType
+
+ if fieldType.Name() == "" { // override type of anonymous structs
+ nestedTypeName := modelName + "." + jsonName
+ prop.Ref = &nestedTypeName
+ b.addModel(fieldType, nestedTypeName)
+ }
+ return jsonName, modelDescription, prop
+}
+
+func hasNamedJSONTag(field reflect.StructField) bool {
+ parts := strings.Split(field.Tag.Get("json"), ",")
+ if len(parts) == 0 {
+ return false
+ }
+ for _, s := range parts[1:] {
+ if s == "inline" {
+ return false
+ }
+ }
+ return len(parts[0]) > 0
+}
+
+func (b modelBuilder) buildStructTypeProperty(field reflect.StructField, jsonName string, model *Model) (nameJson string, prop ModelProperty) {
+ prop.setPropertyMetadata(field)
+ // Check for type override in tag
+ if prop.Type != nil {
+ return jsonName, prop
+ }
+ fieldType := field.Type
+ // check for anonymous
+ if len(fieldType.Name()) == 0 {
+ // anonymous
+ anonType := model.Id + "." + jsonName
+ b.addModel(fieldType, anonType)
+ prop.Ref = &anonType
+ return jsonName, prop
+ }
+
+ if field.Name == fieldType.Name() && field.Anonymous && !hasNamedJSONTag(field) {
+ // embedded struct
+ sub := modelBuilder{new(ModelList), b.Config}
+ sub.addModel(fieldType, "")
+ subKey := sub.keyFrom(fieldType)
+ // merge properties from sub
+ subModel, _ := sub.Models.At(subKey)
+ subModel.Properties.Do(func(k string, v ModelProperty) {
+ model.Properties.Put(k, v)
+ // if subModel says this property is required then include it
+ required := false
+ for _, each := range subModel.Required {
+ if k == each {
+ required = true
+ break
+ }
+ }
+ if required {
+ model.Required = append(model.Required, k)
+ }
+ })
+ // add all new referenced models
+ sub.Models.Do(func(key string, sub Model) {
+ if key != subKey {
+ if _, ok := b.Models.At(key); !ok {
+ b.Models.Put(key, sub)
+ }
+ }
+ })
+ // empty name signals skip property
+ return "", prop
+ }
+ // simple struct
+ b.addModel(fieldType, "")
+ var pType = b.keyFrom(fieldType)
+ prop.Ref = &pType
+ return jsonName, prop
+}
+
+func (b modelBuilder) buildArrayTypeProperty(field reflect.StructField, jsonName, modelName string) (nameJson string, prop ModelProperty) {
+ // check for type override in tags
+ prop.setPropertyMetadata(field)
+ if prop.Type != nil {
+ return jsonName, prop
+ }
+ fieldType := field.Type
+ if fieldType.Elem().Kind() == reflect.Uint8 {
+ stringt := "string"
+ prop.Type = &stringt
+ return jsonName, prop
+ }
+ var pType = "array"
+ prop.Type = &pType
+ isPrimitive := b.isPrimitiveType(fieldType.Elem().Name())
+ elemTypeName := b.getElementTypeName(modelName, jsonName, fieldType.Elem())
+ prop.Items = new(Item)
+ if isPrimitive {
+ mapped := b.jsonSchemaType(elemTypeName)
+ prop.Items.Type = &mapped
+ } else {
+ prop.Items.Ref = &elemTypeName
+ }
+ // add|overwrite model for element type
+ if fieldType.Elem().Kind() == reflect.Ptr {
+ fieldType = fieldType.Elem()
+ }
+ if !isPrimitive {
+ b.addModel(fieldType.Elem(), elemTypeName)
+ }
+ return jsonName, prop
+}
+
+func (b modelBuilder) buildPointerTypeProperty(field reflect.StructField, jsonName, modelName string) (nameJson string, prop ModelProperty) {
+ prop.setPropertyMetadata(field)
+ // Check for type override in tags
+ if prop.Type != nil {
+ return jsonName, prop
+ }
+ fieldType := field.Type
+
+ // override type of pointer to list-likes
+ if fieldType.Elem().Kind() == reflect.Slice || fieldType.Elem().Kind() == reflect.Array {
+ var pType = "array"
+ prop.Type = &pType
+ isPrimitive := b.isPrimitiveType(fieldType.Elem().Elem().Name())
+ elemName := b.getElementTypeName(modelName, jsonName, fieldType.Elem().Elem())
+ if isPrimitive {
+ primName := b.jsonSchemaType(elemName)
+ prop.Items = &Item{Ref: &primName}
+ } else {
+ prop.Items = &Item{Ref: &elemName}
+ }
+ if !isPrimitive {
+ // add|overwrite model for element type
+ b.addModel(fieldType.Elem().Elem(), elemName)
+ }
+ } else {
+ // non-array, pointer type
+ fieldTypeName := b.keyFrom(fieldType.Elem())
+ var pType = b.jsonSchemaType(fieldTypeName) // no star, include pkg path
+ if b.isPrimitiveType(fieldTypeName) {
+ prop.Type = &pType
+ prop.Format = b.jsonSchemaFormat(fieldTypeName)
+ return jsonName, prop
+ }
+ prop.Ref = &pType
+ elemName := ""
+ if fieldType.Elem().Name() == "" {
+ elemName = modelName + "." + jsonName
+ prop.Ref = &elemName
+ }
+ b.addModel(fieldType.Elem(), elemName)
+ }
+ return jsonName, prop
+}
+
+func (b modelBuilder) getElementTypeName(modelName, jsonName string, t reflect.Type) string {
+ if t.Kind() == reflect.Ptr {
+ t = t.Elem()
+ }
+ if t.Name() == "" {
+ return modelName + "." + jsonName
+ }
+ return b.keyFrom(t)
+}
+
+func (b modelBuilder) keyFrom(st reflect.Type) string {
+ key := st.String()
+ if b.Config != nil && b.Config.ModelTypeNameHandler != nil {
+ if name, ok := b.Config.ModelTypeNameHandler(st); ok {
+ key = name
+ }
+ }
+ if len(st.Name()) == 0 { // unnamed type
+ // Swagger UI has special meaning for [
+ key = strings.Replace(key, "[]", "||", -1)
+ }
+ return key
+}
+
+// see also https://golang.org/ref/spec#Numeric_types
+func (b modelBuilder) isPrimitiveType(modelName string) bool {
+ if len(modelName) == 0 {
+ return false
+ }
+ return strings.Contains("uint uint8 uint16 uint32 uint64 int int8 int16 int32 int64 float32 float64 bool string byte rune time.Time", modelName)
+}
+
+// jsonNameOfField returns the name of the field as it should appear in JSON format
+// An empty string indicates that this field is not part of the JSON representation
+func (b modelBuilder) jsonNameOfField(field reflect.StructField) string {
+ if jsonTag := field.Tag.Get("json"); jsonTag != "" {
+ s := strings.Split(jsonTag, ",")
+ if s[0] == "-" {
+ // empty name signals skip property
+ return ""
+ } else if s[0] != "" {
+ return s[0]
+ }
+ }
+ return field.Name
+}
+
+// see also http://json-schema.org/latest/json-schema-core.html#anchor8
+func (b modelBuilder) jsonSchemaType(modelName string) string {
+ schemaMap := map[string]string{
+ "uint": "integer",
+ "uint8": "integer",
+ "uint16": "integer",
+ "uint32": "integer",
+ "uint64": "integer",
+
+ "int": "integer",
+ "int8": "integer",
+ "int16": "integer",
+ "int32": "integer",
+ "int64": "integer",
+
+ "byte": "integer",
+ "float64": "number",
+ "float32": "number",
+ "bool": "boolean",
+ "time.Time": "string",
+ }
+ mapped, ok := schemaMap[modelName]
+ if !ok {
+ return modelName // use as is (custom or struct)
+ }
+ return mapped
+}
+
+func (b modelBuilder) jsonSchemaFormat(modelName string) string {
+ if b.Config != nil && b.Config.SchemaFormatHandler != nil {
+ if mapped := b.Config.SchemaFormatHandler(modelName); mapped != "" {
+ return mapped
+ }
+ }
+ schemaMap := map[string]string{
+ "int": "int32",
+ "int32": "int32",
+ "int64": "int64",
+ "byte": "byte",
+ "uint": "integer",
+ "uint8": "byte",
+ "float64": "double",
+ "float32": "float",
+ "time.Time": "date-time",
+ "*time.Time": "date-time",
+ }
+ mapped, ok := schemaMap[modelName]
+ if !ok {
+ return "" // no format
+ }
+ return mapped
+}