goai_shema.go 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  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. func (s Schema) MarshalJSON() ([]byte, error) {
  62. var (
  63. b []byte
  64. m map[string]json.RawMessage
  65. err error
  66. )
  67. type tempSchema Schema // To prevent JSON marshal recursion error.
  68. if b, err = json.Marshal(tempSchema(s)); err != nil {
  69. return nil, err
  70. }
  71. if err = json.Unmarshal(b, &m); err != nil {
  72. return nil, err
  73. }
  74. for k, v := range s.XExtensions {
  75. if b, err = json.Marshal(v); err != nil {
  76. return nil, err
  77. }
  78. m[k] = b
  79. }
  80. return json.Marshal(m)
  81. }
  82. // Discriminator is specified by OpenAPI/Swagger standard version 3.0.
  83. type Discriminator struct {
  84. PropertyName string `json:"propertyName"`
  85. Mapping map[string]string `json:"mapping,omitempty"`
  86. }
  87. // addSchema creates schemas with objects.
  88. // Note that the `object` can be array alias like: `type Res []Item`.
  89. func (oai *OpenApiV3) addSchema(object ...interface{}) error {
  90. for _, v := range object {
  91. if err := oai.doAddSchemaSingle(v); err != nil {
  92. return err
  93. }
  94. }
  95. return nil
  96. }
  97. func (oai *OpenApiV3) doAddSchemaSingle(object interface{}) error {
  98. if oai.Components.Schemas.refs == nil {
  99. oai.Components.Schemas.refs = gmap.NewListMap()
  100. }
  101. var (
  102. reflectType = reflect.TypeOf(object)
  103. structTypeName = oai.golangTypeToSchemaName(reflectType)
  104. )
  105. // Already added.
  106. if oai.Components.Schemas.Get(structTypeName) != nil {
  107. return nil
  108. }
  109. // Take the holder first.
  110. oai.Components.Schemas.Set(structTypeName, SchemaRef{})
  111. schema, err := oai.structToSchema(object)
  112. if err != nil {
  113. return err
  114. }
  115. oai.Components.Schemas.Set(structTypeName, SchemaRef{
  116. Ref: "",
  117. Value: schema,
  118. })
  119. return nil
  120. }
  121. // structToSchema converts and returns given struct object as Schema.
  122. func (oai *OpenApiV3) structToSchema(object interface{}) (*Schema, error) {
  123. var (
  124. tagMap = gmeta.Data(object)
  125. schema = &Schema{
  126. Properties: createSchemas(),
  127. XExtensions: make(XExtensions),
  128. }
  129. ignoreProperties []interface{}
  130. )
  131. if len(tagMap) > 0 {
  132. if err := oai.tagMapToSchema(tagMap, schema); err != nil {
  133. return nil, err
  134. }
  135. }
  136. if schema.Type != "" && schema.Type != TypeObject {
  137. return schema, nil
  138. }
  139. // []struct.
  140. if utils.IsArray(object) {
  141. schema.Type = TypeArray
  142. subSchemaRef, err := oai.newSchemaRefWithGolangType(reflect.TypeOf(object).Elem(), nil)
  143. if err != nil {
  144. return nil, err
  145. }
  146. schema.Items = subSchemaRef
  147. if len(schema.Enum) > 0 {
  148. schema.Items.Value.Enum = schema.Enum
  149. schema.Enum = nil
  150. }
  151. return schema, nil
  152. }
  153. // struct.
  154. structFields, _ := gstructs.Fields(gstructs.FieldsInput{
  155. Pointer: object,
  156. RecursiveOption: gstructs.RecursiveOptionEmbeddedNoTag,
  157. })
  158. schema.Type = TypeObject
  159. for _, structField := range structFields {
  160. if !gstr.IsLetterUpper(structField.Name()[0]) {
  161. continue
  162. }
  163. var fieldName = structField.Name()
  164. for _, tagName := range gconv.StructTagPriority {
  165. if tagValue := structField.Tag(tagName); tagValue != "" {
  166. fieldName = tagValue
  167. break
  168. }
  169. }
  170. schemaRef, err := oai.newSchemaRefWithGolangType(
  171. structField.Type().Type,
  172. structField.TagMap(),
  173. )
  174. if err != nil {
  175. return nil, err
  176. }
  177. schema.Properties.Set(fieldName, *schemaRef)
  178. }
  179. schema.Properties.Iterator(func(key string, ref SchemaRef) bool {
  180. if ref.Value != nil && ref.Value.ValidationRules != "" {
  181. validationRuleSet := gset.NewStrSetFrom(gstr.Split(ref.Value.ValidationRules, "|"))
  182. if validationRuleSet.Contains(validationRuleKeyForRequired) {
  183. schema.Required = append(schema.Required, key)
  184. }
  185. }
  186. if !isValidParameterName(key) {
  187. ignoreProperties = append(ignoreProperties, key)
  188. }
  189. return true
  190. })
  191. if len(ignoreProperties) > 0 {
  192. schema.Properties.Removes(ignoreProperties)
  193. }
  194. return schema, nil
  195. }
  196. func (oai *OpenApiV3) tagMapToSchema(tagMap map[string]string, schema *Schema) error {
  197. var mergedTagMap = oai.fileMapWithShortTags(tagMap)
  198. if err := gconv.Struct(mergedTagMap, schema); err != nil {
  199. return gerror.Wrap(err, `mapping struct tags to Schema failed`)
  200. }
  201. oai.tagMapToXExtensions(mergedTagMap, schema.XExtensions)
  202. // Validation info to OpenAPI schema pattern.
  203. for _, tag := range gvalid.GetTags() {
  204. if validationTagValue, ok := tagMap[tag]; ok {
  205. _, validationRules, _ := gvalid.ParseTagValue(validationTagValue)
  206. schema.ValidationRules = validationRules
  207. // Enum checks.
  208. if len(schema.Enum) == 0 {
  209. for _, rule := range gstr.SplitAndTrim(validationRules, "|") {
  210. if gstr.HasPrefix(rule, validationRuleKeyForIn) {
  211. var (
  212. isAllEnumNumber = true
  213. enumArray = gstr.SplitAndTrim(rule[len(validationRuleKeyForIn):], ",")
  214. )
  215. for _, enum := range enumArray {
  216. if !gstr.IsNumeric(enum) {
  217. isAllEnumNumber = false
  218. break
  219. }
  220. }
  221. if isAllEnumNumber {
  222. schema.Enum = gconv.Interfaces(gconv.Int64s(enumArray))
  223. } else {
  224. schema.Enum = gconv.Interfaces(enumArray)
  225. }
  226. }
  227. }
  228. }
  229. break
  230. }
  231. }
  232. return nil
  233. }