model_builder.go 12 KB

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