document.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  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. openapi_v2 "github.com/google/gnostic-models/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. // TODO(wrong): a schema with a $ref can have properties. We can ignore them (would be incomplete), but we cannot return an error.
  78. if len(s.GetProperties().GetAdditionalProperties()) > 0 {
  79. return nil, newSchemaError(path, "unallowed embedded type definition")
  80. }
  81. // TODO(wrong): a schema with a $ref can have a type. We can ignore it (would be incomplete), but we cannot return an error.
  82. if len(s.GetType().GetValue()) > 0 {
  83. return nil, newSchemaError(path, "definition reference can't have a type")
  84. }
  85. // TODO(wrong): $refs outside of the definitions are completely valid. We can ignore them (would be incomplete), but we cannot return an error.
  86. if !strings.HasPrefix(s.GetXRef(), "#/definitions/") {
  87. return nil, newSchemaError(path, "unallowed reference to non-definition %q", s.GetXRef())
  88. }
  89. reference := strings.TrimPrefix(s.GetXRef(), "#/definitions/")
  90. if _, ok := d.models[reference]; !ok {
  91. return nil, newSchemaError(path, "unknown model in reference: %q", reference)
  92. }
  93. base, err := d.parseBaseSchema(s, path)
  94. if err != nil {
  95. return nil, err
  96. }
  97. return &Ref{
  98. BaseSchema: base,
  99. reference: reference,
  100. definitions: d,
  101. }, nil
  102. }
  103. func parseDefault(def *openapi_v2.Any) (interface{}, error) {
  104. if def == nil {
  105. return nil, nil
  106. }
  107. var i interface{}
  108. if err := yaml.Unmarshal([]byte(def.Yaml), &i); err != nil {
  109. return nil, err
  110. }
  111. return i, nil
  112. }
  113. func (d *Definitions) parseBaseSchema(s *openapi_v2.Schema, path *Path) (BaseSchema, error) {
  114. def, err := parseDefault(s.GetDefault())
  115. if err != nil {
  116. return BaseSchema{}, err
  117. }
  118. return BaseSchema{
  119. Description: s.GetDescription(),
  120. Default: def,
  121. Extensions: VendorExtensionToMap(s.GetVendorExtension()),
  122. Path: *path,
  123. }, nil
  124. }
  125. // We believe the schema is a map, verify and return a new schema
  126. func (d *Definitions) parseMap(s *openapi_v2.Schema, path *Path) (Schema, error) {
  127. if len(s.GetType().GetValue()) != 0 && s.GetType().GetValue()[0] != object {
  128. return nil, newSchemaError(path, "invalid object type")
  129. }
  130. var sub Schema
  131. // TODO(incomplete): this misses the boolean case as AdditionalProperties is a bool+schema sum type.
  132. if s.GetAdditionalProperties().GetSchema() == nil {
  133. base, err := d.parseBaseSchema(s, path)
  134. if err != nil {
  135. return nil, err
  136. }
  137. sub = &Arbitrary{
  138. BaseSchema: base,
  139. }
  140. } else {
  141. var err error
  142. sub, err = d.ParseSchema(s.GetAdditionalProperties().GetSchema(), path)
  143. if err != nil {
  144. return nil, err
  145. }
  146. }
  147. base, err := d.parseBaseSchema(s, path)
  148. if err != nil {
  149. return nil, err
  150. }
  151. return &Map{
  152. BaseSchema: base,
  153. SubType: sub,
  154. }, nil
  155. }
  156. func (d *Definitions) parsePrimitive(s *openapi_v2.Schema, path *Path) (Schema, error) {
  157. var t string
  158. if len(s.GetType().GetValue()) > 1 {
  159. return nil, newSchemaError(path, "primitive can't have more than 1 type")
  160. }
  161. if len(s.GetType().GetValue()) == 1 {
  162. t = s.GetType().GetValue()[0]
  163. }
  164. switch t {
  165. case String: // do nothing
  166. case Number: // do nothing
  167. case Integer: // do nothing
  168. case Boolean: // do nothing
  169. // TODO(wrong): this misses "null". Would skip the null case (would be incomplete), but we cannot return an error.
  170. default:
  171. return nil, newSchemaError(path, "Unknown primitive type: %q", t)
  172. }
  173. base, err := d.parseBaseSchema(s, path)
  174. if err != nil {
  175. return nil, err
  176. }
  177. return &Primitive{
  178. BaseSchema: base,
  179. Type: t,
  180. Format: s.GetFormat(),
  181. }, nil
  182. }
  183. func (d *Definitions) parseArray(s *openapi_v2.Schema, path *Path) (Schema, error) {
  184. if len(s.GetType().GetValue()) != 1 {
  185. return nil, newSchemaError(path, "array should have exactly one type")
  186. }
  187. if s.GetType().GetValue()[0] != array {
  188. return nil, newSchemaError(path, `array should have type "array"`)
  189. }
  190. if len(s.GetItems().GetSchema()) != 1 {
  191. // TODO(wrong): Items can have multiple elements. We can ignore Items then (would be incomplete), but we cannot return an error.
  192. // TODO(wrong): "type: array" witohut any items at all is completely valid.
  193. return nil, newSchemaError(path, "array should have exactly one sub-item")
  194. }
  195. sub, err := d.ParseSchema(s.GetItems().GetSchema()[0], path)
  196. if err != nil {
  197. return nil, err
  198. }
  199. base, err := d.parseBaseSchema(s, path)
  200. if err != nil {
  201. return nil, err
  202. }
  203. return &Array{
  204. BaseSchema: base,
  205. SubType: sub,
  206. }, nil
  207. }
  208. func (d *Definitions) parseKind(s *openapi_v2.Schema, path *Path) (Schema, error) {
  209. if len(s.GetType().GetValue()) != 0 && s.GetType().GetValue()[0] != object {
  210. return nil, newSchemaError(path, "invalid object type")
  211. }
  212. if s.GetProperties() == nil {
  213. return nil, newSchemaError(path, "object doesn't have properties")
  214. }
  215. fields := map[string]Schema{}
  216. fieldOrder := []string{}
  217. for _, namedSchema := range s.GetProperties().GetAdditionalProperties() {
  218. var err error
  219. name := namedSchema.GetName()
  220. path := path.FieldPath(name)
  221. fields[name], err = d.ParseSchema(namedSchema.GetValue(), &path)
  222. if err != nil {
  223. return nil, err
  224. }
  225. fieldOrder = append(fieldOrder, name)
  226. }
  227. base, err := d.parseBaseSchema(s, path)
  228. if err != nil {
  229. return nil, err
  230. }
  231. return &Kind{
  232. BaseSchema: base,
  233. RequiredFields: s.GetRequired(),
  234. Fields: fields,
  235. FieldOrder: fieldOrder,
  236. }, nil
  237. }
  238. func (d *Definitions) parseArbitrary(s *openapi_v2.Schema, path *Path) (Schema, error) {
  239. base, err := d.parseBaseSchema(s, path)
  240. if err != nil {
  241. return nil, err
  242. }
  243. return &Arbitrary{
  244. BaseSchema: base,
  245. }, nil
  246. }
  247. // ParseSchema creates a walkable Schema from an openapi schema. While
  248. // this function is public, it doesn't leak through the interface.
  249. func (d *Definitions) ParseSchema(s *openapi_v2.Schema, path *Path) (Schema, error) {
  250. if s.GetXRef() != "" {
  251. // TODO(incomplete): ignoring the rest of s is wrong. As long as there are no conflict, everything from s must be considered
  252. // Reference: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#path-item-object
  253. return d.parseReference(s, path)
  254. }
  255. objectTypes := s.GetType().GetValue()
  256. switch len(objectTypes) {
  257. case 0:
  258. // in the OpenAPI schema served by older k8s versions, object definitions created from structs did not include
  259. // the type:object property (they only included the "properties" property), so we need to handle this case
  260. // TODO: validate that we ever published empty, non-nil properties. JSON roundtripping nils them.
  261. if s.GetProperties() != nil {
  262. // TODO(wrong): when verifying a non-object later against this, it will be rejected as invalid type.
  263. // TODO(CRD validation schema publishing): we have to filter properties (empty or not) if type=object is not given
  264. return d.parseKind(s, path)
  265. } else {
  266. // Definition has no type and no properties. Treat it as an arbitrary value
  267. // TODO(incomplete): what if it has additionalProperties=false or patternProperties?
  268. // ANSWER: parseArbitrary is less strict than it has to be with patternProperties (which is ignored). So this is correct (of course not complete).
  269. return d.parseArbitrary(s, path)
  270. }
  271. case 1:
  272. t := objectTypes[0]
  273. switch t {
  274. case object:
  275. if s.GetProperties() != nil {
  276. return d.parseKind(s, path)
  277. } else {
  278. return d.parseMap(s, path)
  279. }
  280. case array:
  281. return d.parseArray(s, path)
  282. }
  283. return d.parsePrimitive(s, path)
  284. default:
  285. // the OpenAPI generator never generates (nor it ever did in the past) OpenAPI type definitions with multiple types
  286. // TODO(wrong): this is rejecting a completely valid OpenAPI spec
  287. // TODO(CRD validation schema publishing): filter these out
  288. return nil, newSchemaError(path, "definitions with multiple types aren't supported")
  289. }
  290. }
  291. // LookupModel is public through the interface of Models. It
  292. // returns a visitable schema from the given model name.
  293. func (d *Definitions) LookupModel(model string) Schema {
  294. return d.models[model]
  295. }
  296. func (d *Definitions) ListModels() []string {
  297. models := []string{}
  298. for model := range d.models {
  299. models = append(models, model)
  300. }
  301. sort.Strings(models)
  302. return models
  303. }
  304. type Ref struct {
  305. BaseSchema
  306. reference string
  307. definitions *Definitions
  308. }
  309. var _ Reference = &Ref{}
  310. func (r *Ref) Reference() string {
  311. return r.reference
  312. }
  313. func (r *Ref) SubSchema() Schema {
  314. return r.definitions.models[r.reference]
  315. }
  316. func (r *Ref) Accept(v SchemaVisitor) {
  317. v.VisitReference(r)
  318. }
  319. func (r *Ref) GetName() string {
  320. return fmt.Sprintf("Reference to %q", r.reference)
  321. }