You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

schema.go 5.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. package analysis
  2. import (
  3. "fmt"
  4. "github.com/go-openapi/spec"
  5. "github.com/go-openapi/strfmt"
  6. )
  7. // SchemaOpts configures the schema analyzer
  8. type SchemaOpts struct {
  9. Schema *spec.Schema
  10. Root interface{}
  11. BasePath string
  12. _ struct{}
  13. }
  14. // Schema analysis, will classify the schema according to known
  15. // patterns.
  16. func Schema(opts SchemaOpts) (*AnalyzedSchema, error) {
  17. if opts.Schema == nil {
  18. return nil, fmt.Errorf("no schema to analyze")
  19. }
  20. a := &AnalyzedSchema{
  21. schema: opts.Schema,
  22. root: opts.Root,
  23. basePath: opts.BasePath,
  24. }
  25. a.initializeFlags()
  26. a.inferKnownType()
  27. a.inferEnum()
  28. a.inferBaseType()
  29. if err := a.inferMap(); err != nil {
  30. return nil, err
  31. }
  32. if err := a.inferArray(); err != nil {
  33. return nil, err
  34. }
  35. a.inferTuple()
  36. if err := a.inferFromRef(); err != nil {
  37. return nil, err
  38. }
  39. a.inferSimpleSchema()
  40. return a, nil
  41. }
  42. // AnalyzedSchema indicates what the schema represents
  43. type AnalyzedSchema struct {
  44. schema *spec.Schema
  45. root interface{}
  46. basePath string
  47. hasProps bool
  48. hasAllOf bool
  49. hasItems bool
  50. hasAdditionalProps bool
  51. hasAdditionalItems bool
  52. hasRef bool
  53. IsKnownType bool
  54. IsSimpleSchema bool
  55. IsArray bool
  56. IsSimpleArray bool
  57. IsMap bool
  58. IsSimpleMap bool
  59. IsExtendedObject bool
  60. IsTuple bool
  61. IsTupleWithExtra bool
  62. IsBaseType bool
  63. IsEnum bool
  64. }
  65. // Inherits copies value fields from other onto this schema
  66. func (a *AnalyzedSchema) inherits(other *AnalyzedSchema) {
  67. if other == nil {
  68. return
  69. }
  70. a.hasProps = other.hasProps
  71. a.hasAllOf = other.hasAllOf
  72. a.hasItems = other.hasItems
  73. a.hasAdditionalItems = other.hasAdditionalItems
  74. a.hasAdditionalProps = other.hasAdditionalProps
  75. a.hasRef = other.hasRef
  76. a.IsKnownType = other.IsKnownType
  77. a.IsSimpleSchema = other.IsSimpleSchema
  78. a.IsArray = other.IsArray
  79. a.IsSimpleArray = other.IsSimpleArray
  80. a.IsMap = other.IsMap
  81. a.IsSimpleMap = other.IsSimpleMap
  82. a.IsExtendedObject = other.IsExtendedObject
  83. a.IsTuple = other.IsTuple
  84. a.IsTupleWithExtra = other.IsTupleWithExtra
  85. a.IsBaseType = other.IsBaseType
  86. a.IsEnum = other.IsEnum
  87. }
  88. func (a *AnalyzedSchema) inferFromRef() error {
  89. if a.hasRef {
  90. sch := new(spec.Schema)
  91. sch.Ref = a.schema.Ref
  92. err := spec.ExpandSchema(sch, a.root, nil)
  93. if err != nil {
  94. return err
  95. }
  96. rsch, err := Schema(SchemaOpts{
  97. Schema: sch,
  98. Root: a.root,
  99. BasePath: a.basePath,
  100. })
  101. if err != nil {
  102. // NOTE(fredbi): currently the only cause for errors is
  103. // unresolved ref. Since spec.ExpandSchema() expands the
  104. // schema recursively, there is no chance to get there,
  105. // until we add more causes for error in this schema analysis.
  106. return err
  107. }
  108. a.inherits(rsch)
  109. }
  110. return nil
  111. }
  112. func (a *AnalyzedSchema) inferSimpleSchema() {
  113. a.IsSimpleSchema = a.IsKnownType || a.IsSimpleArray || a.IsSimpleMap
  114. }
  115. func (a *AnalyzedSchema) inferKnownType() {
  116. tpe := a.schema.Type
  117. format := a.schema.Format
  118. a.IsKnownType = tpe.Contains("boolean") ||
  119. tpe.Contains("integer") ||
  120. tpe.Contains("number") ||
  121. tpe.Contains("string") ||
  122. (format != "" && strfmt.Default.ContainsName(format)) ||
  123. (a.isObjectType() && !a.hasProps && !a.hasAllOf && !a.hasAdditionalProps && !a.hasAdditionalItems)
  124. }
  125. func (a *AnalyzedSchema) inferMap() error {
  126. if a.isObjectType() {
  127. hasExtra := a.hasProps || a.hasAllOf
  128. a.IsMap = a.hasAdditionalProps && !hasExtra
  129. a.IsExtendedObject = a.hasAdditionalProps && hasExtra
  130. if a.IsMap {
  131. if a.schema.AdditionalProperties.Schema != nil {
  132. msch, err := Schema(SchemaOpts{
  133. Schema: a.schema.AdditionalProperties.Schema,
  134. Root: a.root,
  135. BasePath: a.basePath,
  136. })
  137. if err != nil {
  138. return err
  139. }
  140. a.IsSimpleMap = msch.IsSimpleSchema
  141. } else if a.schema.AdditionalProperties.Allows {
  142. a.IsSimpleMap = true
  143. }
  144. }
  145. }
  146. return nil
  147. }
  148. func (a *AnalyzedSchema) inferArray() error {
  149. // an array has Items defined as an object schema, otherwise we qualify this JSON array as a tuple
  150. // (yes, even if the Items array contains only one element).
  151. // arrays in JSON schema may be unrestricted (i.e no Items specified).
  152. // Note that arrays in Swagger MUST have Items. Nonetheless, we analyze unrestricted arrays.
  153. //
  154. // NOTE: the spec package misses the distinction between:
  155. // items: [] and items: {}, so we consider both arrays here.
  156. a.IsArray = a.isArrayType() && (a.schema.Items == nil || a.schema.Items.Schemas == nil)
  157. if a.IsArray && a.hasItems {
  158. if a.schema.Items.Schema != nil {
  159. itsch, err := Schema(SchemaOpts{
  160. Schema: a.schema.Items.Schema,
  161. Root: a.root,
  162. BasePath: a.basePath,
  163. })
  164. if err != nil {
  165. return err
  166. }
  167. a.IsSimpleArray = itsch.IsSimpleSchema
  168. }
  169. }
  170. if a.IsArray && !a.hasItems {
  171. a.IsSimpleArray = true
  172. }
  173. return nil
  174. }
  175. func (a *AnalyzedSchema) inferTuple() {
  176. tuple := a.hasItems && a.schema.Items.Schemas != nil
  177. a.IsTuple = tuple && !a.hasAdditionalItems
  178. a.IsTupleWithExtra = tuple && a.hasAdditionalItems
  179. }
  180. func (a *AnalyzedSchema) inferBaseType() {
  181. if a.isObjectType() {
  182. a.IsBaseType = a.schema.Discriminator != ""
  183. }
  184. }
  185. func (a *AnalyzedSchema) inferEnum() {
  186. a.IsEnum = len(a.schema.Enum) > 0
  187. }
  188. func (a *AnalyzedSchema) initializeFlags() {
  189. a.hasProps = len(a.schema.Properties) > 0
  190. a.hasAllOf = len(a.schema.AllOf) > 0
  191. a.hasRef = a.schema.Ref.String() != ""
  192. a.hasItems = a.schema.Items != nil &&
  193. (a.schema.Items.Schema != nil || len(a.schema.Items.Schemas) > 0)
  194. a.hasAdditionalProps = a.schema.AdditionalProperties != nil &&
  195. (a.schema.AdditionalProperties != nil || a.schema.AdditionalProperties.Allows)
  196. a.hasAdditionalItems = a.schema.AdditionalItems != nil &&
  197. (a.schema.AdditionalItems.Schema != nil || a.schema.AdditionalItems.Allows)
  198. }
  199. func (a *AnalyzedSchema) isObjectType() bool {
  200. return !a.hasRef && (a.schema.Type == nil || a.schema.Type.Contains("") || a.schema.Type.Contains("object"))
  201. }
  202. func (a *AnalyzedSchema) isArrayType() bool {
  203. return !a.hasRef && (a.schema.Type != nil && a.schema.Type.Contains("array"))
  204. }