Commit 74f4fda7 authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #51223 from apelisse/openapi-reference-first-class

Automatic merge from submit-queue (batch tested with PRs 51148, 50816, 49741, 50858, 51223) openapi: Change references to be first-class **What this PR does / why we need it**: References in the openapi are currently completely hidden from the model, and just passed through as we walk the tree. The problem is that they can have a different description and more importantly, different extensions. Change them to be first-class citizen, and fully part of the model. It means that visitors have to implement one more function and decide if something specific should be done with references. Validation is updated to just completely ignore them and passthrough (like it was done before). **Which issue this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close that issue when PR gets merged)*: fixes # **Special notes for your reviewer**: **Release note**: ```release-note NONE ```
parents 909a0984 5ef35167
...@@ -164,8 +164,9 @@ func (d *Definitions) parseReference(s *openapi_v2.Schema, path *Path) (Schema, ...@@ -164,8 +164,9 @@ func (d *Definitions) parseReference(s *openapi_v2.Schema, path *Path) (Schema,
if _, ok := d.models[reference]; !ok { if _, ok := d.models[reference]; !ok {
return nil, newSchemaError(path, "unknown model in reference: %q", reference) return nil, newSchemaError(path, "unknown model in reference: %q", reference)
} }
return &Reference{ return &Ref{
Reference: reference, BaseSchema: d.parseBaseSchema(s, path),
reference: reference,
definitions: d, definitions: d,
}, nil }, nil
} }
...@@ -303,38 +304,27 @@ func (d *Definitions) LookupResource(gvk schema.GroupVersionKind) Schema { ...@@ -303,38 +304,27 @@ func (d *Definitions) LookupResource(gvk schema.GroupVersionKind) Schema {
return model return model
} }
// SchemaReference doesn't match a specific type. It's mostly a type Ref struct {
// pass-through type. BaseSchema
type Reference struct {
Reference string
reference string
definitions *Definitions definitions *Definitions
} }
var _ Schema = &Reference{} var _ Reference = &Ref{}
func (r *Reference) GetSubSchema() Schema { func (r *Ref) Reference() string {
return r.definitions.models[r.Reference] return r.reference
} }
func (r *Reference) Accept(s SchemaVisitor) { func (r *Ref) SubSchema() Schema {
r.GetSubSchema().Accept(s) return r.definitions.models[r.reference]
} }
func (r *Reference) GetDescription() string { func (r *Ref) Accept(v SchemaVisitor) {
return r.GetSubSchema().GetDescription() v.VisitReference(r)
} }
func (r *Reference) GetExtensions() map[string]interface{} { func (r *Ref) GetName() string {
return r.GetSubSchema().GetExtensions() return fmt.Sprintf("Reference to %q", r.reference)
}
func (*Reference) GetPath() *Path {
// Reference never has a path, because it can be referenced from
// multiple locations.
return &Path{}
}
func (r *Reference) GetName() string {
return r.Reference
} }
...@@ -49,11 +49,13 @@ type Resources interface { ...@@ -49,11 +49,13 @@ type Resources interface {
// - Map is a map of string to one and only one given subtype // - Map is a map of string to one and only one given subtype
// - Primitive can be string, integer, number and boolean. // - Primitive can be string, integer, number and boolean.
// - Kind is an object with specific fields mapping to specific types. // - Kind is an object with specific fields mapping to specific types.
// - Reference is a link to another definition.
type SchemaVisitor interface { type SchemaVisitor interface {
VisitArray(*Array) VisitArray(*Array)
VisitMap(*Map) VisitMap(*Map)
VisitPrimitive(*Primitive) VisitPrimitive(*Primitive)
VisitKind(*Kind) VisitKind(*Kind)
VisitReference(Reference)
} }
// Schema is the base definition of an openapi type. // Schema is the base definition of an openapi type.
...@@ -219,3 +221,11 @@ func (p *Primitive) GetName() string { ...@@ -219,3 +221,11 @@ func (p *Primitive) GetName() string {
} }
return fmt.Sprintf("%s (%s)", p.Type, p.Format) return fmt.Sprintf("%s (%s)", p.Type, p.Format)
} }
// Reference implementation depends on the type of document.
type Reference interface {
Schema
Reference() string
SubSchema() Schema
}
...@@ -78,20 +78,20 @@ var _ = Describe("Reading apps/v1beta1/Deployment from openAPIData", func() { ...@@ -78,20 +78,20 @@ var _ = Describe("Reading apps/v1beta1/Deployment from openAPIData", func() {
It("should have a metadata key of type Reference", func() { It("should have a metadata key of type Reference", func() {
Expect(deployment.Fields).To(HaveKey("metadata")) Expect(deployment.Fields).To(HaveKey("metadata"))
key := deployment.Fields["metadata"].(*openapi.Reference) key := deployment.Fields["metadata"].(openapi.Reference)
Expect(key).ToNot(BeNil()) Expect(key).ToNot(BeNil())
Expect(key.Reference).To(Equal("io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta")) Expect(key.Reference()).To(Equal("io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta"))
subSchema := key.GetSubSchema().(*openapi.Kind) subSchema := key.SubSchema().(*openapi.Kind)
Expect(subSchema).ToNot(BeNil()) Expect(subSchema).ToNot(BeNil())
}) })
var status *openapi.Kind var status *openapi.Kind
It("should have a status key of type Reference", func() { It("should have a status key of type Reference", func() {
Expect(deployment.Fields).To(HaveKey("status")) Expect(deployment.Fields).To(HaveKey("status"))
key := deployment.Fields["status"].(*openapi.Reference) key := deployment.Fields["status"].(openapi.Reference)
Expect(key).ToNot(BeNil()) Expect(key).ToNot(BeNil())
Expect(key.Reference).To(Equal("io.k8s.api.apps.v1beta1.DeploymentStatus")) Expect(key.Reference()).To(Equal("io.k8s.api.apps.v1beta1.DeploymentStatus"))
status = key.GetSubSchema().(*openapi.Kind) status = key.SubSchema().(*openapi.Kind)
Expect(status).ToNot(BeNil()) Expect(status).ToNot(BeNil())
}) })
...@@ -106,22 +106,22 @@ var _ = Describe("Reading apps/v1beta1/Deployment from openAPIData", func() { ...@@ -106,22 +106,22 @@ var _ = Describe("Reading apps/v1beta1/Deployment from openAPIData", func() {
Expect(status.Fields).To(HaveKey("conditions")) Expect(status.Fields).To(HaveKey("conditions"))
conditions := status.Fields["conditions"].(*openapi.Array) conditions := status.Fields["conditions"].(*openapi.Array)
Expect(conditions).ToNot(BeNil()) Expect(conditions).ToNot(BeNil())
Expect(conditions.GetName()).To(Equal("Array of io.k8s.api.apps.v1beta1.DeploymentCondition")) Expect(conditions.GetName()).To(Equal(`Array of Reference to "io.k8s.api.apps.v1beta1.DeploymentCondition"`))
Expect(conditions.GetExtensions()).To(Equal(map[string]interface{}{ Expect(conditions.GetExtensions()).To(Equal(map[string]interface{}{
"x-kubernetes-patch-merge-key": "type", "x-kubernetes-patch-merge-key": "type",
"x-kubernetes-patch-strategy": "merge", "x-kubernetes-patch-strategy": "merge",
})) }))
condition := conditions.SubType.(*openapi.Reference) condition := conditions.SubType.(openapi.Reference)
Expect(condition.Reference).To(Equal("io.k8s.api.apps.v1beta1.DeploymentCondition")) Expect(condition.Reference()).To(Equal("io.k8s.api.apps.v1beta1.DeploymentCondition"))
}) })
var spec *openapi.Kind var spec *openapi.Kind
It("should have a spec key of type Reference", func() { It("should have a spec key of type Reference", func() {
Expect(deployment.Fields).To(HaveKey("spec")) Expect(deployment.Fields).To(HaveKey("spec"))
key := deployment.Fields["spec"].(*openapi.Reference) key := deployment.Fields["spec"].(openapi.Reference)
Expect(key).ToNot(BeNil()) Expect(key).ToNot(BeNil())
Expect(key.Reference).To(Equal("io.k8s.api.apps.v1beta1.DeploymentSpec")) Expect(key.Reference()).To(Equal("io.k8s.api.apps.v1beta1.DeploymentSpec"))
spec = key.GetSubSchema().(*openapi.Kind) spec = key.SubSchema().(*openapi.Kind)
Expect(spec).ToNot(BeNil()) Expect(spec).ToNot(BeNil())
}) })
...@@ -132,9 +132,9 @@ var _ = Describe("Reading apps/v1beta1/Deployment from openAPIData", func() { ...@@ -132,9 +132,9 @@ var _ = Describe("Reading apps/v1beta1/Deployment from openAPIData", func() {
It("should have a spec with a PodTemplateSpec sub-field", func() { It("should have a spec with a PodTemplateSpec sub-field", func() {
Expect(spec.Fields).To(HaveKey("template")) Expect(spec.Fields).To(HaveKey("template"))
key := spec.Fields["template"].(*openapi.Reference) key := spec.Fields["template"].(openapi.Reference)
Expect(key).ToNot(BeNil()) Expect(key).ToNot(BeNil())
Expect(key.Reference).To(Equal("io.k8s.api.core.v1.PodTemplateSpec")) Expect(key.Reference()).To(Equal("io.k8s.api.core.v1.PodTemplateSpec"))
}) })
}) })
...@@ -164,10 +164,10 @@ var _ = Describe("Reading authorization.k8s.io/v1/SubjectAccessReview from openA ...@@ -164,10 +164,10 @@ var _ = Describe("Reading authorization.k8s.io/v1/SubjectAccessReview from openA
sar := schema.(*openapi.Kind) sar := schema.(*openapi.Kind)
Expect(sar).ToNot(BeNil()) Expect(sar).ToNot(BeNil())
Expect(sar.Fields).To(HaveKey("spec")) Expect(sar.Fields).To(HaveKey("spec"))
specRef := sar.Fields["spec"].(*openapi.Reference) specRef := sar.Fields["spec"].(openapi.Reference)
Expect(specRef).ToNot(BeNil()) Expect(specRef).ToNot(BeNil())
Expect(specRef.Reference).To(Equal("io.k8s.api.authorization.v1.SubjectAccessReviewSpec")) Expect(specRef.Reference()).To(Equal("io.k8s.api.authorization.v1.SubjectAccessReviewSpec"))
sarspec = specRef.GetSubSchema().(*openapi.Kind) sarspec = specRef.SubSchema().(*openapi.Kind)
Expect(sarspec).ToNot(BeNil()) Expect(sarspec).ToNot(BeNil())
}) })
......
...@@ -127,6 +127,11 @@ func (item *mapItem) VisitKind(schema *openapi.Kind) { ...@@ -127,6 +127,11 @@ func (item *mapItem) VisitKind(schema *openapi.Kind) {
} }
} }
func (item *mapItem) VisitReference(schema openapi.Reference) {
// passthrough
schema.SubSchema().Accept(item)
}
// arrayItem represents a yaml array. // arrayItem represents a yaml array.
type arrayItem struct { type arrayItem struct {
baseItem baseItem
...@@ -165,6 +170,11 @@ func (item *arrayItem) VisitKind(schema *openapi.Kind) { ...@@ -165,6 +170,11 @@ func (item *arrayItem) VisitKind(schema *openapi.Kind) {
item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: "array", Actual: "map"}) item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: "array", Actual: "map"})
} }
func (item *arrayItem) VisitReference(schema openapi.Reference) {
// passthrough
schema.SubSchema().Accept(item)
}
// primitiveItem represents a yaml value. // primitiveItem represents a yaml value.
type primitiveItem struct { type primitiveItem struct {
baseItem baseItem
...@@ -216,6 +226,11 @@ func (item *primitiveItem) VisitKind(schema *openapi.Kind) { ...@@ -216,6 +226,11 @@ func (item *primitiveItem) VisitKind(schema *openapi.Kind) {
item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: "map", Actual: item.Kind}) item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: "map", Actual: item.Kind})
} }
func (item *primitiveItem) VisitReference(schema openapi.Reference) {
// passthrough
schema.SubSchema().Accept(item)
}
// itemFactory creates the relevant item type/visitor based on the current yaml type. // itemFactory creates the relevant item type/visitor based on the current yaml type.
func itemFactory(path openapi.Path, v interface{}) (ValidationItem, error) { func itemFactory(path openapi.Path, v interface{}) (ValidationItem, error) {
// We need to special case for no-type fields in yaml (e.g. empty item in list) // We need to special case for no-type fields in yaml (e.g. empty item in list)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment