Revert "Aggregate OpenAPI specs"

This reverts commit 1a1d9a0394cbdb1d1e2412ae8f0157799eb5329c.

Kubernetes-commit: 88868402b863b1f59a339d3a218bf62c264721ee
This commit is contained in:
mbohlool 2017-07-07 14:51:42 -07:00 committed by Kubernetes Publisher
parent 473678e36a
commit 2de4d08b67
3 changed files with 5 additions and 1026 deletions

View File

@ -27,7 +27,6 @@ import (
"github.com/emicklei/go-restful-swagger12"
"github.com/golang/glog"
"github.com/go-openapi/spec"
"k8s.io/apimachinery/pkg/apimachinery"
"k8s.io/apimachinery/pkg/apimachinery/registered"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -43,7 +42,6 @@ import (
apirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/apiserver/pkg/server/healthz"
"k8s.io/apiserver/pkg/server/openapi"
"k8s.io/apiserver/pkg/server/routes"
restclient "k8s.io/client-go/rest"
)
@ -131,9 +129,6 @@ type GenericAPIServer struct {
swaggerConfig *swagger.Config
openAPIConfig *openapicommon.Config
// Enables updating OpenAPI spec using update method.
OpenAPIService *openapi.OpenAPIService
// PostStartHooks are each called after the server has started listening, in a separate go func for each
// with no guarantee of ordering between them. The map key is a name used for error reporting.
// It may kill the process with a panic if it wishes to by returning an error.
@ -173,9 +168,6 @@ type DelegationTarget interface {
// ListedPaths returns the paths for supporting an index
ListedPaths() []string
// OpenAPISpec returns the OpenAPI spec of the delegation target if exists, nil otherwise.
OpenAPISpec() *spec.Swagger
}
func (s *GenericAPIServer) UnprotectedHandler() http.Handler {
@ -191,9 +183,6 @@ func (s *GenericAPIServer) HealthzChecks() []healthz.HealthzChecker {
func (s *GenericAPIServer) ListedPaths() []string {
return s.listedPathProvider.ListedPaths()
}
func (s *GenericAPIServer) OpenAPISpec() *spec.Swagger {
return s.OpenAPIService.GetSpec()
}
var EmptyDelegate = emptyDelegate{
requestContextMapper: apirequest.NewRequestContextMapper(),
@ -218,9 +207,6 @@ func (s emptyDelegate) ListedPaths() []string {
func (s emptyDelegate) RequestContextMapper() apirequest.RequestContextMapper {
return s.requestContextMapper
}
func (s emptyDelegate) OpenAPISpec() *spec.Swagger {
return nil
}
// RequestContextMapper is exposed so that third party resource storage can be build in a different location.
// TODO refactor third party resource storage
@ -243,22 +229,17 @@ func (s *GenericAPIServer) PrepareRun() preparedGenericAPIServer {
if s.swaggerConfig != nil {
routes.Swagger{Config: s.swaggerConfig}.Install(s.Handler.GoRestfulContainer)
}
s.PrepareOpenAPIService()
if s.openAPIConfig != nil {
routes.OpenAPI{
Config: s.openAPIConfig,
}.Install(s.Handler.GoRestfulContainer, s.Handler.NonGoRestfulMux)
}
s.installHealthz()
return preparedGenericAPIServer{s}
}
// PrepareOpenAPIService installs OpenAPI handler if it does not exists.
func (s *GenericAPIServer) PrepareOpenAPIService() {
if s.openAPIConfig != nil && s.OpenAPIService == nil {
s.OpenAPIService = routes.OpenAPI{
Config: s.openAPIConfig,
}.Install(s.Handler.GoRestfulContainer, s.Handler.NonGoRestfulMux)
}
}
// Run spawns the secure http server. It only returns if stopCh is closed
// or the secure port cannot be listened on initially.
func (s preparedGenericAPIServer) Run(stopCh <-chan struct{}) error {

View File

@ -1,482 +0,0 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package openapi
import (
"fmt"
"strings"
"github.com/go-openapi/spec"
"k8s.io/apimachinery/pkg/conversion"
"k8s.io/apiserver/pkg/util/trie"
)
const (
DEFINITION_PREFIX = "#/definitions/"
)
var cloner = conversion.NewCloner()
// Run a walkRefCallback method on all references of an OpenAPI spec
type walkAllRefs struct {
// walkRefCallback will be called on each reference and the return value
// will replace that reference. This will allow the callers to change
// all/some references of an spec (e.g. useful in renaming definitions).
walkRefCallback func(ref spec.Ref) spec.Ref
// The spec to walk through.
root *spec.Swagger
}
func newWalkAllRefs(walkRef func(ref spec.Ref) spec.Ref, sp *spec.Swagger) *walkAllRefs {
return &walkAllRefs{
walkRefCallback: walkRef,
root: sp,
}
}
func (s *walkAllRefs) walkRef(ref spec.Ref) spec.Ref {
if ref.String() != "" {
refStr := ref.String()
// References that start with #/definitions/ has a definition
// inside the same spec file. If that is the case, walk through
// those definitions too.
// We do not support external references yet.
if strings.HasPrefix(refStr, DEFINITION_PREFIX) {
def := s.root.Definitions[refStr[len(DEFINITION_PREFIX):]]
s.walkSchema(&def)
}
}
return s.walkRefCallback(ref)
}
func (s *walkAllRefs) walkSchema(schema *spec.Schema) {
if schema == nil {
return
}
schema.Ref = s.walkRef(schema.Ref)
for _, v := range schema.Definitions {
s.walkSchema(&v)
}
for _, v := range schema.Properties {
s.walkSchema(&v)
}
for _, v := range schema.PatternProperties {
s.walkSchema(&v)
}
for _, v := range schema.AllOf {
s.walkSchema(&v)
}
for _, v := range schema.AnyOf {
s.walkSchema(&v)
}
for _, v := range schema.OneOf {
s.walkSchema(&v)
}
if schema.Not != nil {
s.walkSchema(schema.Not)
}
if schema.AdditionalProperties != nil && schema.AdditionalProperties.Schema != nil {
s.walkSchema(schema.AdditionalProperties.Schema)
}
if schema.AdditionalItems != nil && schema.AdditionalItems.Schema != nil {
s.walkSchema(schema.AdditionalItems.Schema)
}
if schema.Items != nil {
if schema.Items.Schema != nil {
s.walkSchema(schema.Items.Schema)
}
for _, v := range schema.Items.Schemas {
s.walkSchema(&v)
}
}
}
func (s *walkAllRefs) walkParams(params []spec.Parameter) {
if params == nil {
return
}
for _, param := range params {
param.Ref = s.walkRef(param.Ref)
s.walkSchema(param.Schema)
if param.Items != nil {
param.Items.Ref = s.walkRef(param.Items.Ref)
}
}
}
func (s *walkAllRefs) walkResponse(resp *spec.Response) {
if resp == nil {
return
}
resp.Ref = s.walkRef(resp.Ref)
s.walkSchema(resp.Schema)
}
func (s *walkAllRefs) walkOperation(op *spec.Operation) {
if op == nil {
return
}
s.walkParams(op.Parameters)
if op.Responses == nil {
return
}
s.walkResponse(op.Responses.Default)
for _, r := range op.Responses.StatusCodeResponses {
s.walkResponse(&r)
}
}
func (s *walkAllRefs) Start() {
for _, pathItem := range s.root.Paths.Paths {
s.walkParams(pathItem.Parameters)
s.walkOperation(pathItem.Delete)
s.walkOperation(pathItem.Get)
s.walkOperation(pathItem.Head)
s.walkOperation(pathItem.Options)
s.walkOperation(pathItem.Patch)
s.walkOperation(pathItem.Post)
s.walkOperation(pathItem.Put)
}
}
// FilterSpecByPaths remove unnecessary paths and unused definitions.
func FilterSpecByPaths(sp *spec.Swagger, keepPathPrefixes []string) {
// First remove unwanted paths
prefixes := trie.New(keepPathPrefixes)
orgPaths := sp.Paths
if orgPaths == nil {
return
}
sp.Paths = &spec.Paths{
VendorExtensible: orgPaths.VendorExtensible,
Paths: map[string]spec.PathItem{},
}
for path, pathItem := range orgPaths.Paths {
if !prefixes.HasPrefix(path) {
continue
}
sp.Paths.Paths[path] = pathItem
}
// Walk all references to find all definition references.
usedDefinitions := map[string]bool{}
newWalkAllRefs(func(ref spec.Ref) spec.Ref {
if ref.String() != "" {
refStr := ref.String()
if strings.HasPrefix(refStr, DEFINITION_PREFIX) {
usedDefinitions[refStr[len(DEFINITION_PREFIX):]] = true
}
}
return ref
}, sp).Start()
// Remove unused definitions
orgDefinitions := sp.Definitions
sp.Definitions = spec.Definitions{}
for k, v := range orgDefinitions {
if usedDefinitions[k] {
sp.Definitions[k] = v
}
}
}
func equalSchemaMap(s1, s2 map[string]spec.Schema) bool {
if len(s1) != len(s2) {
return false
}
for k, v := range s1 {
v2, found := s2[k]
if !found {
return false
}
if !EqualSchema(&v, &v2) {
return false
}
}
return true
}
func equalSchemaArray(s1, s2 []spec.Schema) bool {
if s1 == nil || s2 == nil {
return s1 == nil && s2 == nil
}
if len(s1) != len(s2) {
return false
}
for _, v1 := range s1 {
found := false
for _, v2 := range s2 {
if EqualSchema(&v1, &v2) {
found = true
break
}
}
if !found {
return false
}
}
for _, v2 := range s2 {
found := false
for _, v1 := range s1 {
if EqualSchema(&v1, &v2) {
found = true
break
}
}
if !found {
return false
}
}
return true
}
func equalSchemaOrBool(s1, s2 *spec.SchemaOrBool) bool {
if s1 == nil || s2 == nil {
return s1 == s2
}
if s1.Allows != s2.Allows {
return false
}
if !EqualSchema(s1.Schema, s2.Schema) {
return false
}
return true
}
func equalSchemaOrArray(s1, s2 *spec.SchemaOrArray) bool {
if s1 == nil || s2 == nil {
return s1 == s2
}
if !EqualSchema(s1.Schema, s2.Schema) {
return false
}
if !equalSchemaArray(s1.Schemas, s2.Schemas) {
return false
}
return true
}
func equalStringArray(s1, s2 []string) bool {
if len(s1) != len(s2) {
return false
}
for _, v1 := range s1 {
found := false
for _, v2 := range s2 {
if v1 == v2 {
found = true
break
}
}
if !found {
return false
}
}
for _, v2 := range s2 {
found := false
for _, v1 := range s1 {
if v1 == v2 {
found = true
break
}
}
if !found {
return false
}
}
return true
}
func equalFloatPointer(s1, s2 *float64) bool {
if s1 == nil || s2 == nil {
return s1 == s2
}
return *s1 == *s2
}
func equalIntPointer(s1, s2 *int64) bool {
if s1 == nil || s2 == nil {
return s1 == s2
}
return *s1 == *s2
}
// EqualSchema returns true if models have the same properties and references
// even if they have different documentation.
func EqualSchema(s1, s2 *spec.Schema) bool {
if s1 == nil || s2 == nil {
return s1 == s2
}
if s1.Ref.String() != s2.Ref.String() {
return false
}
if !equalSchemaMap(s1.Definitions, s2.Definitions) {
return false
}
if !equalSchemaMap(s1.Properties, s2.Properties) {
fmt.Println("Not equal props")
return false
}
if !equalSchemaMap(s1.PatternProperties, s2.PatternProperties) {
return false
}
if !equalSchemaArray(s1.AllOf, s2.AllOf) {
return false
}
if !equalSchemaArray(s1.AnyOf, s2.AnyOf) {
return false
}
if !equalSchemaArray(s1.OneOf, s2.OneOf) {
return false
}
if !EqualSchema(s1.Not, s2.Not) {
return false
}
if !equalSchemaOrBool(s1.AdditionalProperties, s2.AdditionalProperties) {
return false
}
if !equalSchemaOrBool(s1.AdditionalItems, s2.AdditionalItems) {
return false
}
if !equalSchemaOrArray(s1.Items, s2.Items) {
return false
}
if !equalStringArray(s1.Type, s2.Type) {
return false
}
if s1.Format != s2.Format {
return false
}
if !equalFloatPointer(s1.Minimum, s2.Minimum) {
return false
}
if !equalFloatPointer(s1.Maximum, s2.Maximum) {
return false
}
if s1.ExclusiveMaximum != s2.ExclusiveMaximum {
return false
}
if s1.ExclusiveMinimum != s2.ExclusiveMinimum {
return false
}
if !equalFloatPointer(s1.MultipleOf, s2.MultipleOf) {
return false
}
if !equalIntPointer(s1.MaxLength, s2.MaxLength) {
return false
}
if !equalIntPointer(s1.MinLength, s2.MinLength) {
return false
}
if !equalIntPointer(s1.MaxItems, s2.MaxItems) {
return false
}
if !equalIntPointer(s1.MinItems, s2.MinItems) {
return false
}
if s1.Pattern != s2.Pattern {
return false
}
if s1.UniqueItems != s2.UniqueItems {
return false
}
if !equalIntPointer(s1.MaxProperties, s2.MaxProperties) {
return false
}
if !equalIntPointer(s1.MinProperties, s2.MinProperties) {
return false
}
if !equalStringArray(s1.Required, s2.Required) {
return false
}
return len(s1.Enum) == 0 && len(s2.Enum) == 0 && len(s1.Dependencies) == 0 && len(s2.Dependencies) == 0
}
func renameDefinition(s *spec.Swagger, old, new string) {
old_ref := DEFINITION_PREFIX + old
new_ref := DEFINITION_PREFIX + new
newWalkAllRefs(func(ref spec.Ref) spec.Ref {
if ref.String() == old_ref {
return spec.MustCreateRef(new_ref)
}
return ref
}, s).Start()
s.Definitions[new] = s.Definitions[old]
delete(s.Definitions, old)
}
// Copy paths and definitions from source to dest, rename definitions if needed.
// dest will be mutated, and source will not be changed.
func MergeSpecs(dest, source *spec.Swagger) error {
source, err := CloneSpec(source)
if err != nil {
return err
}
for k, v := range source.Paths.Paths {
if _, found := dest.Paths.Paths[k]; found {
return fmt.Errorf("Unable to merge: Duplicated path %s", k)
}
dest.Paths.Paths[k] = v
}
usedNames := map[string]bool{}
for k := range dest.Definitions {
usedNames[k] = true
}
type Rename struct {
from, to string
}
renames := []Rename{}
for k, v := range source.Definitions {
v2, found := dest.Definitions[k]
if found || usedNames[k] {
if found && EqualSchema(&v, &v2) {
continue
}
i := 2
newName := fmt.Sprintf("%s_v%d", k, i)
for usedNames[newName] {
i += 1
newName = fmt.Sprintf("%s_v%d", k, i)
}
renames = append(renames, Rename{from: k, to: newName})
usedNames[newName] = true
} else {
usedNames[k] = true
}
}
for _, r := range renames {
renameDefinition(source, r.from, r.to)
}
for k, v := range source.Definitions {
if _, found := dest.Definitions[k]; !found {
dest.Definitions[k] = v
}
}
return nil
}
// Clone OpenAPI spec
func CloneSpec(source *spec.Swagger) (*spec.Swagger, error) {
if ret, err := cloner.DeepCopy(source); err != nil {
return nil, err
} else {
return ret.(*spec.Swagger), nil
}
}

View File

@ -1,520 +0,0 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package openapi
import (
"testing"
"github.com/ghodss/yaml"
"github.com/go-openapi/spec"
"github.com/stretchr/testify/assert"
)
func TestFilterSpecs(t *testing.T) {
var spec1, spec1_filtered *spec.Swagger
yaml.Unmarshal([]byte(`
swagger: "2.0"
paths:
/test:
post:
tags:
- "test"
summary: "Test API"
operationId: "addTest"
parameters:
- in: "body"
name: "body"
description: "test object"
required: true
schema:
$ref: "#/definitions/Test"
responses:
405:
description: "Invalid input"
$ref: "#/definitions/InvalidInput"
/othertest:
post:
tags:
- "test2"
summary: "Test2 API"
operationId: "addTest2"
consumes:
- "application/json"
produces:
- "application/xml"
parameters:
- in: "body"
name: "body"
description: "test2 object"
required: true
schema:
$ref: "#/definitions/Test2"
definitions:
Test:
type: "object"
properties:
id:
type: "integer"
format: "int64"
status:
type: "string"
description: "Status"
InvalidInput:
type: "string"
format: "string"
Test2:
type: "object"
properties:
other:
$ref: "#/definitions/Other"
Other:
type: "string"
`), &spec1)
yaml.Unmarshal([]byte(`
swagger: "2.0"
paths:
/test:
post:
tags:
- "test"
summary: "Test API"
operationId: "addTest"
parameters:
- in: "body"
name: "body"
description: "test object"
required: true
schema:
$ref: "#/definitions/Test"
responses:
405:
description: "Invalid input"
$ref: "#/definitions/InvalidInput"
definitions:
Test:
type: "object"
properties:
id:
type: "integer"
format: "int64"
status:
type: "string"
description: "Status"
InvalidInput:
type: "string"
format: "string"
`), &spec1_filtered)
assert := assert.New(t)
FilterSpecByPaths(spec1, []string{"/test"})
assert.Equal(spec1_filtered, spec1)
}
func TestMergeSpecsSimple(t *testing.T) {
var spec1, spec2, expected *spec.Swagger
yaml.Unmarshal([]byte(`
swagger: "2.0"
paths:
/test:
post:
tags:
- "test"
summary: "Test API"
operationId: "addTest"
parameters:
- in: "body"
name: "body"
description: "test object"
required: true
schema:
$ref: "#/definitions/Test"
responses:
405:
description: "Invalid input"
$ref: "#/definitions/InvalidInput"
definitions:
Test:
type: "object"
properties:
id:
type: "integer"
format: "int64"
status:
type: "string"
description: "Status"
InvalidInput:
type: "string"
format: "string"
`), &spec1)
yaml.Unmarshal([]byte(`
swagger: "2.0"
paths:
/othertest:
post:
tags:
- "test2"
summary: "Test2 API"
operationId: "addTest2"
consumes:
- "application/json"
produces:
- "application/xml"
parameters:
- in: "body"
name: "body"
description: "test2 object"
required: true
schema:
$ref: "#/definitions/Test2"
definitions:
Test2:
type: "object"
properties:
other:
$ref: "#/definitions/Other"
Other:
type: "string"
`), &spec2)
yaml.Unmarshal([]byte(`
swagger: "2.0"
paths:
/test:
post:
tags:
- "test"
summary: "Test API"
operationId: "addTest"
parameters:
- in: "body"
name: "body"
description: "test object"
required: true
schema:
$ref: "#/definitions/Test"
responses:
405:
description: "Invalid input"
$ref: "#/definitions/InvalidInput"
/othertest:
post:
tags:
- "test2"
summary: "Test2 API"
operationId: "addTest2"
consumes:
- "application/json"
produces:
- "application/xml"
parameters:
- in: "body"
name: "body"
description: "test2 object"
required: true
schema:
$ref: "#/definitions/Test2"
definitions:
Test:
type: "object"
properties:
id:
type: "integer"
format: "int64"
status:
type: "string"
description: "Status"
InvalidInput:
type: "string"
format: "string"
Test2:
type: "object"
properties:
other:
$ref: "#/definitions/Other"
Other:
type: "string"
`), &expected)
assert := assert.New(t)
if !assert.NoError(MergeSpecs(spec1, spec2)) {
return
}
assert.Equal(expected, spec1)
}
func TestMergeSpecsReuseModel(t *testing.T) {
var spec1, spec2, expected *spec.Swagger
yaml.Unmarshal([]byte(`
swagger: "2.0"
paths:
/test:
post:
tags:
- "test"
summary: "Test API"
operationId: "addTest"
parameters:
- in: "body"
name: "body"
description: "test object"
required: true
schema:
$ref: "#/definitions/Test"
responses:
405:
description: "Invalid input"
$ref: "#/definitions/InvalidInput"
definitions:
Test:
type: "object"
properties:
id:
type: "integer"
format: "int64"
status:
type: "string"
description: "Status"
InvalidInput:
type: "string"
format: "string"
`), &spec1)
yaml.Unmarshal([]byte(`
swagger: "2.0"
paths:
/othertest:
post:
tags:
- "test2"
summary: "Test2 API"
operationId: "addTest2"
consumes:
- "application/json"
produces:
- "application/xml"
parameters:
- in: "body"
name: "body"
description: "test2 object"
required: true
schema:
$ref: "#/definitions/Test"
definitions:
Test:
description: "This Test has a description"
type: "object"
properties:
id:
type: "integer"
format: "int64"
status:
type: "string"
description: "This status has another description"
InvalidInput:
type: "string"
format: "string"
`), &spec2)
yaml.Unmarshal([]byte(`
swagger: "2.0"
paths:
/test:
post:
tags:
- "test"
summary: "Test API"
operationId: "addTest"
parameters:
- in: "body"
name: "body"
description: "test object"
required: true
schema:
$ref: "#/definitions/Test"
responses:
405:
description: "Invalid input"
$ref: "#/definitions/InvalidInput"
/othertest:
post:
tags:
- "test2"
summary: "Test2 API"
operationId: "addTest2"
consumes:
- "application/json"
produces:
- "application/xml"
parameters:
- in: "body"
name: "body"
description: "test2 object"
required: true
schema:
$ref: "#/definitions/Test"
definitions:
Test:
type: "object"
properties:
id:
type: "integer"
format: "int64"
status:
type: "string"
description: "Status"
InvalidInput:
type: "string"
format: "string"
`), &expected)
assert := assert.New(t)
if !assert.NoError(MergeSpecs(spec1, spec2)) {
return
}
assert.Equal(expected, spec1)
}
func TestMergeSpecsRenameModel(t *testing.T) {
var spec1, spec2, expected *spec.Swagger
yaml.Unmarshal([]byte(`
swagger: "2.0"
paths:
/test:
post:
tags:
- "test"
summary: "Test API"
operationId: "addTest"
parameters:
- in: "body"
name: "body"
description: "test object"
required: true
schema:
$ref: "#/definitions/Test"
responses:
405:
description: "Invalid input"
$ref: "#/definitions/InvalidInput"
definitions:
Test:
type: "object"
properties:
id:
type: "integer"
format: "int64"
status:
type: "string"
description: "Status"
InvalidInput:
type: "string"
format: "string"
`), &spec1)
yaml.Unmarshal([]byte(`
swagger: "2.0"
paths:
/othertest:
post:
tags:
- "test2"
summary: "Test2 API"
operationId: "addTest2"
consumes:
- "application/json"
produces:
- "application/xml"
parameters:
- in: "body"
name: "body"
description: "test2 object"
required: true
schema:
$ref: "#/definitions/Test"
definitions:
Test:
description: "This Test has a description"
type: "object"
properties:
id:
type: "integer"
format: "int64"
InvalidInput:
type: "string"
format: "string"
`), &spec2)
yaml.Unmarshal([]byte(`
swagger: "2.0"
paths:
/test:
post:
tags:
- "test"
summary: "Test API"
operationId: "addTest"
parameters:
- in: "body"
name: "body"
description: "test object"
required: true
schema:
$ref: "#/definitions/Test"
responses:
405:
description: "Invalid input"
$ref: "#/definitions/InvalidInput"
/othertest:
post:
tags:
- "test2"
summary: "Test2 API"
operationId: "addTest2"
consumes:
- "application/json"
produces:
- "application/xml"
parameters:
- in: "body"
name: "body"
description: "test2 object"
required: true
schema:
$ref: "#/definitions/Test_v2"
definitions:
Test:
type: "object"
properties:
id:
type: "integer"
format: "int64"
status:
type: "string"
description: "Status"
Test_v2:
description: "This Test has a description"
type: "object"
properties:
id:
type: "integer"
format: "int64"
InvalidInput:
type: "string"
format: "string"
`), &expected)
assert := assert.New(t)
if !assert.NoError(MergeSpecs(spec1, spec2)) {
return
}
expected_yaml, _ := yaml.Marshal(expected)
spec1_yaml, _ := yaml.Marshal(spec1)
assert.Equal(string(expected_yaml), string(spec1_yaml))
}