goai_shema.go 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. // Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
  2. //
  3. // This Source Code Form is subject to the terms of the MIT License.
  4. // If a copy of the MIT was not distributed with this file,
  5. // You can obtain one at https://github.com/gogf/gf.
  6. package goai
  7. import (
  8. "reflect"
  9. "github.com/gogf/gf/v2/container/gmap"
  10. "github.com/gogf/gf/v2/container/gset"
  11. "github.com/gogf/gf/v2/errors/gerror"
  12. "github.com/gogf/gf/v2/internal/json"
  13. "github.com/gogf/gf/v2/internal/utils"
  14. "github.com/gogf/gf/v2/os/gstructs"
  15. "github.com/gogf/gf/v2/text/gstr"
  16. "github.com/gogf/gf/v2/util/gconv"
  17. "github.com/gogf/gf/v2/util/gmeta"
  18. "github.com/gogf/gf/v2/util/gvalid"
  19. )
  20. // Schema is specified by OpenAPI/Swagger 3.0 standard.
  21. type Schema struct {
  22. OneOf SchemaRefs `json:"oneOf,omitempty"`
  23. AnyOf SchemaRefs `json:"anyOf,omitempty"`
  24. AllOf SchemaRefs `json:"allOf,omitempty"`
  25. Not *SchemaRef `json:"not,omitempty"`
  26. Type string `json:"type,omitempty"`
  27. Title string `json:"title,omitempty"`
  28. Format string `json:"format,omitempty"`
  29. Description string `json:"description,omitempty"`
  30. Enum []interface{} `json:"enum,omitempty"`
  31. Default interface{} `json:"default,omitempty"`
  32. Example interface{} `json:"example,omitempty"`
  33. ExternalDocs *ExternalDocs `json:"externalDocs,omitempty"`
  34. UniqueItems bool `json:"uniqueItems,omitempty"`
  35. ExclusiveMin bool `json:"exclusiveMinimum,omitempty"`
  36. ExclusiveMax bool `json:"exclusiveMaximum,omitempty"`
  37. Nullable bool `json:"nullable,omitempty"`
  38. ReadOnly bool `json:"readOnly,omitempty"`
  39. WriteOnly bool `json:"writeOnly,omitempty"`
  40. AllowEmptyValue bool `json:"allowEmptyValue,omitempty"`
  41. XML interface{} `json:"xml,omitempty"`
  42. Deprecated bool `json:"deprecated,omitempty"`
  43. Min *float64 `json:"minimum,omitempty"`
  44. Max *float64 `json:"maximum,omitempty"`
  45. MultipleOf *float64 `json:"multipleOf,omitempty"`
  46. MinLength uint64 `json:"minLength,omitempty"`
  47. MaxLength *uint64 `json:"maxLength,omitempty"`
  48. Pattern string `json:"pattern,omitempty"`
  49. MinItems uint64 `json:"minItems,omitempty"`
  50. MaxItems *uint64 `json:"maxItems,omitempty"`
  51. Items *SchemaRef `json:"items,omitempty"`
  52. Required []string `json:"required,omitempty"`
  53. Properties Schemas `json:"properties,omitempty"`
  54. MinProps uint64 `json:"minProperties,omitempty"`
  55. MaxProps *uint64 `json:"maxProperties,omitempty"`
  56. AdditionalProperties *SchemaRef `json:"additionalProperties,omitempty"`
  57. Discriminator *Discriminator `json:"discriminator,omitempty"`
  58. XExtensions XExtensions `json:"-"`
  59. ValidationRules string `json:"-"`
  60. }
  61. // Clone only clones necessary attributes.
  62. // TODO clone all attributes, or improve package deepcopy.
  63. func (s *Schema) Clone() *Schema {
  64. newSchema := *s
  65. newSchema.Required = make([]string, len(s.Required))
  66. copy(newSchema.Required, s.Required)
  67. newSchema.Properties = s.Properties.Clone()
  68. return &newSchema
  69. }
  70. func (s Schema) MarshalJSON() ([]byte, error) {
  71. var (
  72. b []byte
  73. m map[string]json.RawMessage
  74. err error
  75. )
  76. type tempSchema Schema // To prevent JSON marshal recursion error.
  77. if b, err = json.Marshal(tempSchema(s)); err != nil {
  78. return nil, err
  79. }
  80. if err = json.Unmarshal(b, &m); err != nil {
  81. return nil, err
  82. }
  83. for k, v := range s.XExtensions {
  84. if b, err = json.Marshal(v); err != nil {
  85. return nil, err
  86. }
  87. m[k] = b
  88. }
  89. return json.Marshal(m)
  90. }
  91. // Discriminator is specified by OpenAPI/Swagger standard version 3.0.
  92. type Discriminator struct {
  93. PropertyName string `json:"propertyName"`
  94. Mapping map[string]string `json:"mapping,omitempty"`
  95. }
  96. // addSchema creates schemas with objects.
  97. // Note that the `object` can be array alias like: `type Res []Item`.
  98. func (oai *OpenApiV3) addSchema(object ...interface{}) error {
  99. for _, v := range object {
  100. if err := oai.doAddSchemaSingle(v); err != nil {
  101. return err
  102. }
  103. }
  104. return nil
  105. }
  106. func (oai *OpenApiV3) doAddSchemaSingle(object interface{}) error {
  107. if oai.Components.Schemas.refs == nil {
  108. oai.Components.Schemas.refs = gmap.NewListMap()
  109. }
  110. var (
  111. reflectType = reflect.TypeOf(object)
  112. structTypeName = oai.golangTypeToSchemaName(reflectType)
  113. )
  114. // Already added.
  115. if oai.Components.Schemas.Get(structTypeName) != nil {
  116. return nil
  117. }
  118. // Take the holder first.
  119. oai.Components.Schemas.Set(structTypeName, SchemaRef{})
  120. schema, err := oai.structToSchema(object)
  121. if err != nil {
  122. return err
  123. }
  124. oai.Components.Schemas.Set(structTypeName, SchemaRef{
  125. Ref: "",
  126. Value: schema,
  127. })
  128. return nil
  129. }
  130. // structToSchema converts and returns given struct object as Schema.
  131. func (oai *OpenApiV3) structToSchema(object interface{}) (*Schema, error) {
  132. var (
  133. tagMap = gmeta.Data(object)
  134. schema = &Schema{
  135. Properties: createSchemas(),
  136. XExtensions: make(XExtensions),
  137. }
  138. ignoreProperties []interface{}
  139. )
  140. if len(tagMap) > 0 {
  141. if err := oai.tagMapToSchema(tagMap, schema); err != nil {
  142. return nil, err
  143. }
  144. }
  145. if schema.Type != "" && schema.Type != TypeObject {
  146. return schema, nil
  147. }
  148. // []struct.
  149. if utils.IsArray(object) {
  150. schema.Type = TypeArray
  151. subSchemaRef, err := oai.newSchemaRefWithGolangType(reflect.TypeOf(object).Elem(), nil)
  152. if err != nil {
  153. return nil, err
  154. }
  155. schema.Items = subSchemaRef
  156. if len(schema.Enum) > 0 {
  157. schema.Items.Value.Enum = schema.Enum
  158. schema.Enum = nil
  159. }
  160. return schema, nil
  161. }
  162. // struct.
  163. structFields, _ := gstructs.Fields(gstructs.FieldsInput{
  164. Pointer: object,
  165. RecursiveOption: gstructs.RecursiveOptionEmbeddedNoTag,
  166. })
  167. schema.Type = TypeObject
  168. for _, structField := range structFields {
  169. if !gstr.IsLetterUpper(structField.Name()[0]) {
  170. continue
  171. }
  172. var fieldName = structField.Name()
  173. for _, tagName := range gconv.StructTagPriority {
  174. if tagValue := structField.Tag(tagName); tagValue != "" {
  175. fieldName = tagValue
  176. break
  177. }
  178. }
  179. fieldName = gstr.Split(gstr.Trim(fieldName), ",")[0]
  180. if fieldName == "" {
  181. fieldName = structField.Name()
  182. }
  183. schemaRef, err := oai.newSchemaRefWithGolangType(
  184. structField.Type().Type,
  185. structField.TagMap(),
  186. )
  187. if err != nil {
  188. return nil, err
  189. }
  190. schema.Properties.Set(fieldName, *schemaRef)
  191. }
  192. schema.Properties.Iterator(func(key string, ref SchemaRef) bool {
  193. if ref.Value != nil && ref.Value.ValidationRules != "" {
  194. validationRuleSet := gset.NewStrSetFrom(gstr.Split(ref.Value.ValidationRules, "|"))
  195. if validationRuleSet.Contains(validationRuleKeyForRequired) {
  196. schema.Required = append(schema.Required, key)
  197. }
  198. }
  199. if !isValidParameterName(key) {
  200. ignoreProperties = append(ignoreProperties, key)
  201. }
  202. return true
  203. })
  204. if len(ignoreProperties) > 0 {
  205. schema.Properties.Removes(ignoreProperties)
  206. }
  207. return schema, nil
  208. }
  209. func (oai *OpenApiV3) tagMapToSchema(tagMap map[string]string, schema *Schema) error {
  210. var mergedTagMap = oai.fillMapWithShortTags(tagMap)
  211. if err := gconv.Struct(mergedTagMap, schema); err != nil {
  212. return gerror.Wrap(err, `mapping struct tags to Schema failed`)
  213. }
  214. oai.tagMapToXExtensions(mergedTagMap, schema.XExtensions)
  215. // Validation info to OpenAPI schema pattern.
  216. for _, tag := range gvalid.GetTags() {
  217. if validationTagValue, ok := tagMap[tag]; ok {
  218. _, validationRules, _ := gvalid.ParseTagValue(validationTagValue)
  219. schema.ValidationRules = validationRules
  220. // Enum checks.
  221. if len(schema.Enum) == 0 {
  222. for _, rule := range gstr.SplitAndTrim(validationRules, "|") {
  223. if gstr.HasPrefix(rule, validationRuleKeyForIn) {
  224. var (
  225. isAllEnumNumber = true
  226. enumArray = gstr.SplitAndTrim(rule[len(validationRuleKeyForIn):], ",")
  227. )
  228. for _, enum := range enumArray {
  229. if !gstr.IsNumeric(enum) {
  230. isAllEnumNumber = false
  231. break
  232. }
  233. }
  234. if isAllEnumNumber {
  235. schema.Enum = gconv.Interfaces(gconv.Int64s(enumArray))
  236. } else {
  237. schema.Enum = gconv.Interfaces(enumArray)
  238. }
  239. }
  240. }
  241. }
  242. break
  243. }
  244. }
  245. return nil
  246. }