schema.go 6.0 KB

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