document.go 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. /*
  2. Copyright 2017 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package proto
  14. import (
  15. "fmt"
  16. "sort"
  17. "strings"
  18. "github.com/googleapis/gnostic/OpenAPIv2"
  19. "gopkg.in/yaml.v2"
  20. )
  21. func newSchemaError(path *Path, format string, a ...interface{}) error {
  22. err := fmt.Sprintf(format, a...)
  23. if path.Len() == 0 {
  24. return fmt.Errorf("SchemaError: %v", err)
  25. }
  26. return fmt.Errorf("SchemaError(%v): %v", path, err)
  27. }
  28. // VendorExtensionToMap converts openapi VendorExtension to a map.
  29. func VendorExtensionToMap(e []*openapi_v2.NamedAny) map[string]interface{} {
  30. values := map[string]interface{}{}
  31. for _, na := range e {
  32. if na.GetName() == "" || na.GetValue() == nil {
  33. continue
  34. }
  35. if na.GetValue().GetYaml() == "" {
  36. continue
  37. }
  38. var value interface{}
  39. err := yaml.Unmarshal([]byte(na.GetValue().GetYaml()), &value)
  40. if err != nil {
  41. continue
  42. }
  43. values[na.GetName()] = value
  44. }
  45. return values
  46. }
  47. // Definitions is an implementation of `Models`. It looks for
  48. // models in an openapi Schema.
  49. type Definitions struct {
  50. models map[string]Schema
  51. }
  52. var _ Models = &Definitions{}
  53. // NewOpenAPIData creates a new `Models` out of the openapi document.
  54. func NewOpenAPIData(doc *openapi_v2.Document) (Models, error) {
  55. definitions := Definitions{
  56. models: map[string]Schema{},
  57. }
  58. // Save the list of all models first. This will allow us to
  59. // validate that we don't have any dangling reference.
  60. for _, namedSchema := range doc.GetDefinitions().GetAdditionalProperties() {
  61. definitions.models[namedSchema.GetName()] = nil
  62. }
  63. // Now, parse each model. We can validate that references exists.
  64. for _, namedSchema := range doc.GetDefinitions().GetAdditionalProperties() {
  65. path := NewPath(namedSchema.GetName())
  66. schema, err := definitions.ParseSchema(namedSchema.GetValue(), &path)
  67. if err != nil {
  68. return nil, err
  69. }
  70. definitions.models[namedSchema.GetName()] = schema
  71. }
  72. return &definitions, nil
  73. }
  74. // We believe the schema is a reference, verify that and returns a new
  75. // Schema
  76. func (d *Definitions) parseReference(s *openapi_v2.Schema, path *Path) (Schema, error) {
  77. if len(s.GetProperties().GetAdditionalProperties()) > 0 {
  78. return nil, newSchemaError(path, "unallowed embedded type definition")
  79. }
  80. if len(s.GetType().GetValue()) > 0 {
  81. return nil, newSchemaError(path, "definition reference can't have a type")
  82. }
  83. if !strings.HasPrefix(s.GetXRef(), "#/definitions/") {
  84. return nil, newSchemaError(path, "unallowed reference to non-definition %q", s.GetXRef())
  85. }
  86. reference := strings.TrimPrefix(s.GetXRef(), "#/definitions/")
  87. if _, ok := d.models[reference]; !ok {
  88. return nil, newSchemaError(path, "unknown model in reference: %q", reference)
  89. }
  90. return &Ref{
  91. BaseSchema: d.parseBaseSchema(s, path),
  92. reference: reference,
  93. definitions: d,
  94. }, nil
  95. }
  96. func (d *Definitions) parseBaseSchema(s *openapi_v2.Schema, path *Path) BaseSchema {
  97. return BaseSchema{
  98. Description: s.GetDescription(),
  99. Extensions: VendorExtensionToMap(s.GetVendorExtension()),
  100. Path: *path,
  101. }
  102. }
  103. // We believe the schema is a map, verify and return a new schema
  104. func (d *Definitions) parseMap(s *openapi_v2.Schema, path *Path) (Schema, error) {
  105. if len(s.GetType().GetValue()) != 0 && s.GetType().GetValue()[0] != object {
  106. return nil, newSchemaError(path, "invalid object type")
  107. }
  108. var sub Schema
  109. if s.GetAdditionalProperties().GetSchema() == nil {
  110. sub = &Arbitrary{
  111. BaseSchema: d.parseBaseSchema(s, path),
  112. }
  113. } else {
  114. var err error
  115. sub, err = d.ParseSchema(s.GetAdditionalProperties().GetSchema(), path)
  116. if err != nil {
  117. return nil, err
  118. }
  119. }
  120. return &Map{
  121. BaseSchema: d.parseBaseSchema(s, path),
  122. SubType: sub,
  123. }, nil
  124. }
  125. func (d *Definitions) parsePrimitive(s *openapi_v2.Schema, path *Path) (Schema, error) {
  126. var t string
  127. if len(s.GetType().GetValue()) > 1 {
  128. return nil, newSchemaError(path, "primitive can't have more than 1 type")
  129. }
  130. if len(s.GetType().GetValue()) == 1 {
  131. t = s.GetType().GetValue()[0]
  132. }
  133. switch t {
  134. case String: // do nothing
  135. case Number: // do nothing
  136. case Integer: // do nothing
  137. case Boolean: // do nothing
  138. default:
  139. return nil, newSchemaError(path, "Unknown primitive type: %q", t)
  140. }
  141. return &Primitive{
  142. BaseSchema: d.parseBaseSchema(s, path),
  143. Type: t,
  144. Format: s.GetFormat(),
  145. }, nil
  146. }
  147. func (d *Definitions) parseArray(s *openapi_v2.Schema, path *Path) (Schema, error) {
  148. if len(s.GetType().GetValue()) != 1 {
  149. return nil, newSchemaError(path, "array should have exactly one type")
  150. }
  151. if s.GetType().GetValue()[0] != array {
  152. return nil, newSchemaError(path, `array should have type "array"`)
  153. }
  154. if len(s.GetItems().GetSchema()) != 1 {
  155. return nil, newSchemaError(path, "array should have exactly one sub-item")
  156. }
  157. sub, err := d.ParseSchema(s.GetItems().GetSchema()[0], path)
  158. if err != nil {
  159. return nil, err
  160. }
  161. return &Array{
  162. BaseSchema: d.parseBaseSchema(s, path),
  163. SubType: sub,
  164. }, nil
  165. }
  166. func (d *Definitions) parseKind(s *openapi_v2.Schema, path *Path) (Schema, error) {
  167. if len(s.GetType().GetValue()) != 0 && s.GetType().GetValue()[0] != object {
  168. return nil, newSchemaError(path, "invalid object type")
  169. }
  170. if s.GetProperties() == nil {
  171. return nil, newSchemaError(path, "object doesn't have properties")
  172. }
  173. fields := map[string]Schema{}
  174. fieldOrder := []string{}
  175. for _, namedSchema := range s.GetProperties().GetAdditionalProperties() {
  176. var err error
  177. name := namedSchema.GetName()
  178. path := path.FieldPath(name)
  179. fields[name], err = d.ParseSchema(namedSchema.GetValue(), &path)
  180. if err != nil {
  181. return nil, err
  182. }
  183. fieldOrder = append(fieldOrder, name)
  184. }
  185. return &Kind{
  186. BaseSchema: d.parseBaseSchema(s, path),
  187. RequiredFields: s.GetRequired(),
  188. Fields: fields,
  189. FieldOrder: fieldOrder,
  190. }, nil
  191. }
  192. func (d *Definitions) parseArbitrary(s *openapi_v2.Schema, path *Path) (Schema, error) {
  193. return &Arbitrary{
  194. BaseSchema: d.parseBaseSchema(s, path),
  195. }, nil
  196. }
  197. // ParseSchema creates a walkable Schema from an openapi schema. While
  198. // this function is public, it doesn't leak through the interface.
  199. func (d *Definitions) ParseSchema(s *openapi_v2.Schema, path *Path) (Schema, error) {
  200. if s.GetXRef() != "" {
  201. return d.parseReference(s, path)
  202. }
  203. objectTypes := s.GetType().GetValue()
  204. switch len(objectTypes) {
  205. case 0:
  206. // in the OpenAPI schema served by older k8s versions, object definitions created from structs did not include
  207. // the type:object property (they only included the "properties" property), so we need to handle this case
  208. if s.GetProperties() != nil {
  209. return d.parseKind(s, path)
  210. } else {
  211. // Definition has no type and no properties. Treat it as an arbitrary value
  212. // TODO: what if it has additionalProperties or patternProperties?
  213. return d.parseArbitrary(s, path)
  214. }
  215. case 1:
  216. t := objectTypes[0]
  217. switch t {
  218. case object:
  219. if s.GetProperties() != nil {
  220. return d.parseKind(s, path)
  221. } else {
  222. return d.parseMap(s, path)
  223. }
  224. case array:
  225. return d.parseArray(s, path)
  226. }
  227. return d.parsePrimitive(s, path)
  228. default:
  229. // the OpenAPI generator never generates (nor it ever did in the past) OpenAPI type definitions with multiple types
  230. return nil, newSchemaError(path, "definitions with multiple types aren't supported")
  231. }
  232. }
  233. // LookupModel is public through the interface of Models. It
  234. // returns a visitable schema from the given model name.
  235. func (d *Definitions) LookupModel(model string) Schema {
  236. return d.models[model]
  237. }
  238. func (d *Definitions) ListModels() []string {
  239. models := []string{}
  240. for model := range d.models {
  241. models = append(models, model)
  242. }
  243. sort.Strings(models)
  244. return models
  245. }
  246. type Ref struct {
  247. BaseSchema
  248. reference string
  249. definitions *Definitions
  250. }
  251. var _ Reference = &Ref{}
  252. func (r *Ref) Reference() string {
  253. return r.reference
  254. }
  255. func (r *Ref) SubSchema() Schema {
  256. return r.definitions.models[r.reference]
  257. }
  258. func (r *Ref) Accept(v SchemaVisitor) {
  259. v.VisitReference(r)
  260. }
  261. func (r *Ref) GetName() string {
  262. return fmt.Sprintf("Reference to %q", r.reference)
  263. }