model_builder.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. package swagger
  2. import (
  3. "encoding/json"
  4. "reflect"
  5. "strings"
  6. )
  7. // ModelBuildable is used for extending Structs that need more control over
  8. // how the Model appears in the Swagger api declaration.
  9. type ModelBuildable interface {
  10. PostBuildModel(m *Model) *Model
  11. }
  12. type modelBuilder struct {
  13. Models *ModelList
  14. Config *Config
  15. }
  16. type documentable interface {
  17. SwaggerDoc() map[string]string
  18. }
  19. // Check if this structure has a method with signature func (<theModel>) SwaggerDoc() map[string]string
  20. // If it exists, retrive the documentation and overwrite all struct tag descriptions
  21. func getDocFromMethodSwaggerDoc2(model reflect.Type) map[string]string {
  22. if docable, ok := reflect.New(model).Elem().Interface().(documentable); ok {
  23. return docable.SwaggerDoc()
  24. }
  25. return make(map[string]string)
  26. }
  27. // addModelFrom creates and adds a Model to the builder and detects and calls
  28. // the post build hook for customizations
  29. func (b modelBuilder) addModelFrom(sample interface{}) {
  30. if modelOrNil := b.addModel(reflect.TypeOf(sample), ""); modelOrNil != nil {
  31. // allow customizations
  32. if buildable, ok := sample.(ModelBuildable); ok {
  33. modelOrNil = buildable.PostBuildModel(modelOrNil)
  34. b.Models.Put(modelOrNil.Id, *modelOrNil)
  35. }
  36. }
  37. }
  38. func (b modelBuilder) addModel(st reflect.Type, nameOverride string) *Model {
  39. // Turn pointers into simpler types so further checks are
  40. // correct.
  41. if st.Kind() == reflect.Ptr {
  42. st = st.Elem()
  43. }
  44. modelName := b.keyFrom(st)
  45. if nameOverride != "" {
  46. modelName = nameOverride
  47. }
  48. // no models needed for primitive types
  49. if b.isPrimitiveType(modelName) {
  50. return nil
  51. }
  52. // golang encoding/json packages says array and slice values encode as
  53. // JSON arrays, except that []byte encodes as a base64-encoded string.
  54. // If we see a []byte here, treat it at as a primitive type (string)
  55. // and deal with it in buildArrayTypeProperty.
  56. if (st.Kind() == reflect.Slice || st.Kind() == reflect.Array) &&
  57. st.Elem().Kind() == reflect.Uint8 {
  58. return nil
  59. }
  60. // see if we already have visited this model
  61. if _, ok := b.Models.At(modelName); ok {
  62. return nil
  63. }
  64. sm := Model{
  65. Id: modelName,
  66. Required: []string{},
  67. Properties: ModelPropertyList{}}
  68. // reference the model before further initializing (enables recursive structs)
  69. b.Models.Put(modelName, sm)
  70. // check for slice or array
  71. if st.Kind() == reflect.Slice || st.Kind() == reflect.Array {
  72. b.addModel(st.Elem(), "")
  73. return &sm
  74. }
  75. // check for structure or primitive type
  76. if st.Kind() != reflect.Struct {
  77. return &sm
  78. }
  79. fullDoc := getDocFromMethodSwaggerDoc2(st)
  80. modelDescriptions := []string{}
  81. for i := 0; i < st.NumField(); i++ {
  82. field := st.Field(i)
  83. jsonName, modelDescription, prop := b.buildProperty(field, &sm, modelName)
  84. if len(modelDescription) > 0 {
  85. modelDescriptions = append(modelDescriptions, modelDescription)
  86. }
  87. // add if not omitted
  88. if len(jsonName) != 0 {
  89. // update description
  90. if fieldDoc, ok := fullDoc[jsonName]; ok {
  91. prop.Description = fieldDoc
  92. }
  93. // update Required
  94. if b.isPropertyRequired(field) {
  95. sm.Required = append(sm.Required, jsonName)
  96. }
  97. sm.Properties.Put(jsonName, prop)
  98. }
  99. }
  100. // We always overwrite documentation if SwaggerDoc method exists
  101. // "" is special for documenting the struct itself
  102. if modelDoc, ok := fullDoc[""]; ok {
  103. sm.Description = modelDoc
  104. } else if len(modelDescriptions) != 0 {
  105. sm.Description = strings.Join(modelDescriptions, "\n")
  106. }
  107. // update model builder with completed model
  108. b.Models.Put(modelName, sm)
  109. return &sm
  110. }
  111. func (b modelBuilder) isPropertyRequired(field reflect.StructField) bool {
  112. required := true
  113. if jsonTag := field.Tag.Get("json"); jsonTag != "" {
  114. s := strings.Split(jsonTag, ",")
  115. if len(s) > 1 && s[1] == "omitempty" {
  116. return false
  117. }
  118. }
  119. return required
  120. }
  121. func (b modelBuilder) buildProperty(field reflect.StructField, model *Model, modelName string) (jsonName, modelDescription string, prop ModelProperty) {
  122. jsonName = b.jsonNameOfField(field)
  123. if len(jsonName) == 0 {
  124. // empty name signals skip property
  125. return "", "", prop
  126. }
  127. if field.Name == "XMLName" && field.Type.String() == "xml.Name" {
  128. // property is metadata for the xml.Name attribute, can be skipped
  129. return "", "", prop
  130. }
  131. if tag := field.Tag.Get("modelDescription"); tag != "" {
  132. modelDescription = tag
  133. }
  134. prop.setPropertyMetadata(field)
  135. if prop.Type != nil {
  136. return jsonName, modelDescription, prop
  137. }
  138. fieldType := field.Type
  139. // check if type is doing its own marshalling
  140. marshalerType := reflect.TypeOf((*json.Marshaler)(nil)).Elem()
  141. if fieldType.Implements(marshalerType) {
  142. var pType = "string"
  143. if prop.Type == nil {
  144. prop.Type = &pType
  145. }
  146. if prop.Format == "" {
  147. prop.Format = b.jsonSchemaFormat(b.keyFrom(fieldType))
  148. }
  149. return jsonName, modelDescription, prop
  150. }
  151. // check if annotation says it is a string
  152. if jsonTag := field.Tag.Get("json"); jsonTag != "" {
  153. s := strings.Split(jsonTag, ",")
  154. if len(s) > 1 && s[1] == "string" {
  155. stringt := "string"
  156. prop.Type = &stringt
  157. return jsonName, modelDescription, prop
  158. }
  159. }
  160. fieldKind := fieldType.Kind()
  161. switch {
  162. case fieldKind == reflect.Struct:
  163. jsonName, prop := b.buildStructTypeProperty(field, jsonName, model)
  164. return jsonName, modelDescription, prop
  165. case fieldKind == reflect.Slice || fieldKind == reflect.Array:
  166. jsonName, prop := b.buildArrayTypeProperty(field, jsonName, modelName)
  167. return jsonName, modelDescription, prop
  168. case fieldKind == reflect.Ptr:
  169. jsonName, prop := b.buildPointerTypeProperty(field, jsonName, modelName)
  170. return jsonName, modelDescription, prop
  171. case fieldKind == reflect.String:
  172. stringt := "string"
  173. prop.Type = &stringt
  174. return jsonName, modelDescription, prop
  175. case fieldKind == reflect.Map:
  176. // if it's a map, it's unstructured, and swagger 1.2 can't handle it
  177. objectType := "object"
  178. prop.Type = &objectType
  179. return jsonName, modelDescription, prop
  180. }
  181. fieldTypeName := b.keyFrom(fieldType)
  182. if b.isPrimitiveType(fieldTypeName) {
  183. mapped := b.jsonSchemaType(fieldTypeName)
  184. prop.Type = &mapped
  185. prop.Format = b.jsonSchemaFormat(fieldTypeName)
  186. return jsonName, modelDescription, prop
  187. }
  188. modelType := b.keyFrom(fieldType)
  189. prop.Ref = &modelType
  190. if fieldType.Name() == "" { // override type of anonymous structs
  191. nestedTypeName := modelName + "." + jsonName
  192. prop.Ref = &nestedTypeName
  193. b.addModel(fieldType, nestedTypeName)
  194. }
  195. return jsonName, modelDescription, prop
  196. }
  197. func hasNamedJSONTag(field reflect.StructField) bool {
  198. parts := strings.Split(field.Tag.Get("json"), ",")
  199. if len(parts) == 0 {
  200. return false
  201. }
  202. for _, s := range parts[1:] {
  203. if s == "inline" {
  204. return false
  205. }
  206. }
  207. return len(parts[0]) > 0
  208. }
  209. func (b modelBuilder) buildStructTypeProperty(field reflect.StructField, jsonName string, model *Model) (nameJson string, prop ModelProperty) {
  210. prop.setPropertyMetadata(field)
  211. // Check for type override in tag
  212. if prop.Type != nil {
  213. return jsonName, prop
  214. }
  215. fieldType := field.Type
  216. // check for anonymous
  217. if len(fieldType.Name()) == 0 {
  218. // anonymous
  219. anonType := model.Id + "." + jsonName
  220. b.addModel(fieldType, anonType)
  221. prop.Ref = &anonType
  222. return jsonName, prop
  223. }
  224. if field.Name == fieldType.Name() && field.Anonymous && !hasNamedJSONTag(field) {
  225. // embedded struct
  226. sub := modelBuilder{new(ModelList), b.Config}
  227. sub.addModel(fieldType, "")
  228. subKey := sub.keyFrom(fieldType)
  229. // merge properties from sub
  230. subModel, _ := sub.Models.At(subKey)
  231. subModel.Properties.Do(func(k string, v ModelProperty) {
  232. model.Properties.Put(k, v)
  233. // if subModel says this property is required then include it
  234. required := false
  235. for _, each := range subModel.Required {
  236. if k == each {
  237. required = true
  238. break
  239. }
  240. }
  241. if required {
  242. model.Required = append(model.Required, k)
  243. }
  244. })
  245. // add all new referenced models
  246. sub.Models.Do(func(key string, sub Model) {
  247. if key != subKey {
  248. if _, ok := b.Models.At(key); !ok {
  249. b.Models.Put(key, sub)
  250. }
  251. }
  252. })
  253. // empty name signals skip property
  254. return "", prop
  255. }
  256. // simple struct
  257. b.addModel(fieldType, "")
  258. var pType = b.keyFrom(fieldType)
  259. prop.Ref = &pType
  260. return jsonName, prop
  261. }
  262. func (b modelBuilder) buildArrayTypeProperty(field reflect.StructField, jsonName, modelName string) (nameJson string, prop ModelProperty) {
  263. // check for type override in tags
  264. prop.setPropertyMetadata(field)
  265. if prop.Type != nil {
  266. return jsonName, prop
  267. }
  268. fieldType := field.Type
  269. if fieldType.Elem().Kind() == reflect.Uint8 {
  270. stringt := "string"
  271. prop.Type = &stringt
  272. return jsonName, prop
  273. }
  274. var pType = "array"
  275. prop.Type = &pType
  276. isPrimitive := b.isPrimitiveType(fieldType.Elem().Name())
  277. elemTypeName := b.getElementTypeName(modelName, jsonName, fieldType.Elem())
  278. prop.Items = new(Item)
  279. if isPrimitive {
  280. mapped := b.jsonSchemaType(elemTypeName)
  281. prop.Items.Type = &mapped
  282. } else {
  283. prop.Items.Ref = &elemTypeName
  284. }
  285. // add|overwrite model for element type
  286. if fieldType.Elem().Kind() == reflect.Ptr {
  287. fieldType = fieldType.Elem()
  288. }
  289. if !isPrimitive {
  290. b.addModel(fieldType.Elem(), elemTypeName)
  291. }
  292. return jsonName, prop
  293. }
  294. func (b modelBuilder) buildPointerTypeProperty(field reflect.StructField, jsonName, modelName string) (nameJson string, prop ModelProperty) {
  295. prop.setPropertyMetadata(field)
  296. // Check for type override in tags
  297. if prop.Type != nil {
  298. return jsonName, prop
  299. }
  300. fieldType := field.Type
  301. // override type of pointer to list-likes
  302. if fieldType.Elem().Kind() == reflect.Slice || fieldType.Elem().Kind() == reflect.Array {
  303. var pType = "array"
  304. prop.Type = &pType
  305. isPrimitive := b.isPrimitiveType(fieldType.Elem().Elem().Name())
  306. elemName := b.getElementTypeName(modelName, jsonName, fieldType.Elem().Elem())
  307. if isPrimitive {
  308. primName := b.jsonSchemaType(elemName)
  309. prop.Items = &Item{Ref: &primName}
  310. } else {
  311. prop.Items = &Item{Ref: &elemName}
  312. }
  313. if !isPrimitive {
  314. // add|overwrite model for element type
  315. b.addModel(fieldType.Elem().Elem(), elemName)
  316. }
  317. } else {
  318. // non-array, pointer type
  319. fieldTypeName := b.keyFrom(fieldType.Elem())
  320. var pType = b.jsonSchemaType(fieldTypeName) // no star, include pkg path
  321. if b.isPrimitiveType(fieldTypeName) {
  322. prop.Type = &pType
  323. prop.Format = b.jsonSchemaFormat(fieldTypeName)
  324. return jsonName, prop
  325. }
  326. prop.Ref = &pType
  327. elemName := ""
  328. if fieldType.Elem().Name() == "" {
  329. elemName = modelName + "." + jsonName
  330. prop.Ref = &elemName
  331. }
  332. b.addModel(fieldType.Elem(), elemName)
  333. }
  334. return jsonName, prop
  335. }
  336. func (b modelBuilder) getElementTypeName(modelName, jsonName string, t reflect.Type) string {
  337. if t.Kind() == reflect.Ptr {
  338. t = t.Elem()
  339. }
  340. if t.Name() == "" {
  341. return modelName + "." + jsonName
  342. }
  343. return b.keyFrom(t)
  344. }
  345. func (b modelBuilder) keyFrom(st reflect.Type) string {
  346. key := st.String()
  347. if b.Config != nil && b.Config.ModelTypeNameHandler != nil {
  348. if name, ok := b.Config.ModelTypeNameHandler(st); ok {
  349. key = name
  350. }
  351. }
  352. if len(st.Name()) == 0 { // unnamed type
  353. // Swagger UI has special meaning for [
  354. key = strings.Replace(key, "[]", "||", -1)
  355. }
  356. return key
  357. }
  358. // see also https://golang.org/ref/spec#Numeric_types
  359. func (b modelBuilder) isPrimitiveType(modelName string) bool {
  360. if len(modelName) == 0 {
  361. return false
  362. }
  363. return strings.Contains("uint uint8 uint16 uint32 uint64 int int8 int16 int32 int64 float32 float64 bool string byte rune time.Time", modelName)
  364. }
  365. // jsonNameOfField returns the name of the field as it should appear in JSON format
  366. // An empty string indicates that this field is not part of the JSON representation
  367. func (b modelBuilder) jsonNameOfField(field reflect.StructField) string {
  368. if jsonTag := field.Tag.Get("json"); jsonTag != "" {
  369. s := strings.Split(jsonTag, ",")
  370. if s[0] == "-" {
  371. // empty name signals skip property
  372. return ""
  373. } else if s[0] != "" {
  374. return s[0]
  375. }
  376. }
  377. return field.Name
  378. }
  379. // see also http://json-schema.org/latest/json-schema-core.html#anchor8
  380. func (b modelBuilder) jsonSchemaType(modelName string) string {
  381. schemaMap := map[string]string{
  382. "uint": "integer",
  383. "uint8": "integer",
  384. "uint16": "integer",
  385. "uint32": "integer",
  386. "uint64": "integer",
  387. "int": "integer",
  388. "int8": "integer",
  389. "int16": "integer",
  390. "int32": "integer",
  391. "int64": "integer",
  392. "byte": "integer",
  393. "float64": "number",
  394. "float32": "number",
  395. "bool": "boolean",
  396. "time.Time": "string",
  397. }
  398. mapped, ok := schemaMap[modelName]
  399. if !ok {
  400. return modelName // use as is (custom or struct)
  401. }
  402. return mapped
  403. }
  404. func (b modelBuilder) jsonSchemaFormat(modelName string) string {
  405. if b.Config != nil && b.Config.SchemaFormatHandler != nil {
  406. if mapped := b.Config.SchemaFormatHandler(modelName); mapped != "" {
  407. return mapped
  408. }
  409. }
  410. schemaMap := map[string]string{
  411. "int": "int32",
  412. "int32": "int32",
  413. "int64": "int64",
  414. "byte": "byte",
  415. "uint": "integer",
  416. "uint8": "byte",
  417. "float64": "double",
  418. "float32": "float",
  419. "time.Time": "date-time",
  420. "*time.Time": "date-time",
  421. }
  422. mapped, ok := schemaMap[modelName]
  423. if !ok {
  424. return "" // no format
  425. }
  426. return mapped
  427. }