Skip to content

Commit

Permalink
Merge pull request #349 from snyk/fix/merge-operations
Browse files Browse the repository at this point in the history
fix: merge operations from duplicate paths
  • Loading branch information
jgresty authored Jul 10, 2024
2 parents 53cbbbf + 6044113 commit 7de0754
Show file tree
Hide file tree
Showing 7 changed files with 187 additions and 29 deletions.
37 changes: 23 additions & 14 deletions collator.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,27 +272,36 @@ func (c *Collator) mergePaths(rv *ResourceVersion) error {
}
var errs error
for k, v := range rv.T.Paths {
route := routeForPath(k)
if _, ok := c.seenRoutes[route]; ok {
if c.useFirstRoute {
continue
for opName, opValue := range v.Operations() {
route := routeForPath(k, opName)
if _, ok := c.seenRoutes[route]; ok {
if c.useFirstRoute {
continue
} else {
errs = multierr.Append(
errs,
fmt.Errorf("conflict in #/paths %s: declared in both %s and %s", k, rv.path, c.pathSources[k]),
)
}
} else {
errs = multierr.Append(
errs,
fmt.Errorf("conflict in #/paths %s: declared in both %s and %s", k, rv.path, c.pathSources[k]),
)
c.seenRoutes[route] = struct{}{}
if c.result.Paths[k] == nil {
// Path doesn't exist in output
c.result.Paths[k] = v
} else {
// There is another operation on this path, merge the
// current operation into that one
c.result.Paths[k].SetOperation(opName, opValue)
}
c.pathSources[k] = rv.path
}
} else {
c.seenRoutes[route] = struct{}{}
c.result.Paths[k] = v
c.pathSources[k] = rv.path
}
}
return errs
}

var routeForPathRE = regexp.MustCompile(`\{[^}]*\}`)

func routeForPath(path string) string {
return routeForPathRE.ReplaceAllString(path, "{}")
func routeForPath(path, operation string) string {
return fmt.Sprintf("%s %s", operation, routeForPathRE.ReplaceAllString(path, "{}"))
}
28 changes: 28 additions & 0 deletions collator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,3 +217,31 @@ func TestCollateMergingResources(t *testing.T) {
result := collator.Result()
c.Assert(result.Paths["/orgs/{org_id}/projects/{project_id}"].Delete.Responses["204"], qt.IsNotNil)
}

func TestCollateOperationsOnSamePath(t *testing.T) {
c := qt.New(t)
collator := vervet.NewCollator(vervet.UseFirstRoute(true))
examples1, err := vervet.LoadResourceVersions(testdata.Path("operation-change/_examples"))
c.Assert(err, qt.IsNil)
examples1v, err := examples1.At("2021-06-15~experimental")
c.Assert(err, qt.IsNil)

examples2, err := vervet.LoadResourceVersions(testdata.Path("operation-change/_examples2"))
c.Assert(err, qt.IsNil)
examples2v, err := examples2.At("2021-06-15~experimental")
c.Assert(err, qt.IsNil)

err = collator.Collate(examples1v)
c.Assert(err, qt.IsNil)
err = collator.Collate(examples2v)
c.Assert(err, qt.IsNil)

result := collator.Result()

c.Assert(result.Paths["/examples/hello-world"].Get, qt.Not(qt.IsNil))
c.Assert(result.Paths["/examples/hello-world"].Get.Description, qt.Contains, " - from example 1")
c.Assert(result.Paths["/examples/hello-world"].Post, qt.Not(qt.IsNil))
c.Assert(result.Paths["/examples/hello-world"].Post.Description, qt.Contains, " - from example 1")
c.Assert(result.Paths["/examples/hello-world"].Put, qt.Not(qt.IsNil))
c.Assert(result.Paths["/examples/hello-world"].Put.Description, qt.Contains, " - from example 2")
}
8 changes: 4 additions & 4 deletions internal/scraper/gcs_scraper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ func TestGCSScraper(t *testing.T) {
tests := []struct {
service, version, digest string
}{
{"petfood", "2021-09-01", "sha256:I20cAQ3VEjDrY7O0B678yq+0pYN2h3sxQy7vmdlo4+w="},
{"animals", "2021-10-16", "sha256:P1FEFvnhtxJSqXr/p6fMNKE+HYwN6iwKccBGHIVZbyg="},
{"petfood", "2021-09-01", "sha256:zCgJaPeR8R21wsAlYn46xO6NE3XJiyFtLnYrP4DpM3U="},
{"animals", "2021-10-16", "sha256:hcv2i7awT6CcSCecw9WrYBokFyzYNVaQArGgqHqdj7s="},
}

cfg := &config.ServerConfig{
Expand Down Expand Up @@ -95,8 +95,8 @@ func TestGCSScraperCollation(t *testing.T) {
tests := []struct {
service, version, digest string
}{
{"petfood", "2021-09-01", "sha256:I20cAQ3VEjDrY7O0B678yq+0pYN2h3sxQy7vmdlo4+w="},
{"animals", "2021-10-16", "sha256:P1FEFvnhtxJSqXr/p6fMNKE+HYwN6iwKccBGHIVZbyg="},
{"petfood", "2021-09-01", "sha256:zCgJaPeR8R21wsAlYn46xO6NE3XJiyFtLnYrP4DpM3U="},
{"animals", "2021-10-16", "sha256:hcv2i7awT6CcSCecw9WrYBokFyzYNVaQArGgqHqdj7s="},
}

cfg := &config.ServerConfig{
Expand Down
8 changes: 4 additions & 4 deletions internal/scraper/s3_scraper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ func TestS3Scraper(t *testing.T) {
tests := []struct {
name, version, digest string
}{
{"petfood", "2021-09-01", "sha256:I20cAQ3VEjDrY7O0B678yq+0pYN2h3sxQy7vmdlo4+w="},
{"animals", "2021-10-16", "sha256:P1FEFvnhtxJSqXr/p6fMNKE+HYwN6iwKccBGHIVZbyg="},
{"petfood", "2021-09-01", "sha256:zCgJaPeR8R21wsAlYn46xO6NE3XJiyFtLnYrP4DpM3U="},
{"animals", "2021-10-16", "sha256:hcv2i7awT6CcSCecw9WrYBokFyzYNVaQArGgqHqdj7s="},
}

cfg := &config.ServerConfig{
Expand Down Expand Up @@ -90,9 +90,9 @@ func TestS3ScraperCollation(t *testing.T) {
tests := []struct {
name, version, digest string
}{{
"petfood", "2021-09-01", "sha256:I20cAQ3VEjDrY7O0B678yq+0pYN2h3sxQy7vmdlo4+w=",
"petfood", "2021-09-01", "sha256:zCgJaPeR8R21wsAlYn46xO6NE3XJiyFtLnYrP4DpM3U=",
}, {
"animals", "2021-10-16", "sha256:P1FEFvnhtxJSqXr/p6fMNKE+HYwN6iwKccBGHIVZbyg=",
"animals", "2021-10-16", "sha256:hcv2i7awT6CcSCecw9WrYBokFyzYNVaQArGgqHqdj7s=",
}}

cfg := &config.ServerConfig{
Expand Down
14 changes: 7 additions & 7 deletions internal/scraper/scraper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,16 @@ var (
petfood = &testService{
versions: []string{"2021-09-01", "2021-09-16"},
contents: map[string]string{
"2021-09-01": `{"paths":{"/crickets": {}}}`,
"2021-09-16": `{"paths":{"/crickets": {}, "/kibble": {}}}`,
"2021-09-01": `{"paths":{"/crickets": {"get": {}}}}`,
"2021-09-16": `{"paths":{"/crickets": {"get": {}}, "/kibble": {"get": {}}}}`,
},
}
animals = &testService{
versions: []string{"2021-01-01", "2021-10-01", "2021-10-16"},
contents: map[string]string{
"2021-01-01": `{"paths":{"/legacy": {}}}`,
"2021-10-01": `{"paths":{"/geckos": {}}}`,
"2021-10-16": `{"paths":{"/geckos": {}, "/puppies": {}}}`,
"2021-01-01": `{"paths":{"/legacy": {"get": {}}}}`,
"2021-10-01": `{"paths":{"/geckos": {"get": {}}}}`,
"2021-10-16": `{"paths":{"/geckos": {"get": {}}, "/puppies": {"get": {}}}}`,
},
}
)
Expand Down Expand Up @@ -85,8 +85,8 @@ func TestScraper(t *testing.T) {
tests := []struct {
name, version, digest string
}{
{"petfood", "2021-09-01", "sha256:I20cAQ3VEjDrY7O0B678yq+0pYN2h3sxQy7vmdlo4+w="},
{"animals", "2021-10-16", "sha256:P1FEFvnhtxJSqXr/p6fMNKE+HYwN6iwKccBGHIVZbyg="},
{"petfood", "2021-09-01", "sha256:zCgJaPeR8R21wsAlYn46xO6NE3XJiyFtLnYrP4DpM3U="},
{"animals", "2021-10-16", "sha256:hcv2i7awT6CcSCecw9WrYBokFyzYNVaQArGgqHqdj7s="},
}

cfg := &config.ServerConfig{
Expand Down
52 changes: 52 additions & 0 deletions testdata/operation-change/_examples/2021-06-15/spec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
openapi: 3.0.3
x-snyk-api-stability: beta
info:
title: Registry
version: 3.0.0
servers:
- url: /api/v3
description: Snyk Registry
paths:
/examples/hello-world:
post:
description: Create a single result from the hello-world example - from example 1
operationId: helloWorldCreate
requestBody:
content:
application/vnd.api+json:
schema:
type: object
properties:
attributes:
type: object
properties:
message:
type: string
betaField:
type: string
additionalProperties: false
required: ['message', 'betaField']
additionalProperties: false
required: ['attributes']
responses:
'201':
description: 'A hello world entity being requested is returned'
content:
application/vnd.api+json:
schema:
type: object
required: ['jsonapi', 'data', 'links']
additionalProperties: false
get:
description: Get a list of hello-worlds example - from example 1
operationId: helloWorldGetList
responses:
'200':
description: 'A hello world entity being requested is returned'
content:
application/vnd.api+json:
schema:
type: object
required: ['jsonapi', 'data', 'links']
additionalProperties: false
69 changes: 69 additions & 0 deletions testdata/operation-change/_examples2/2021-06-15/spec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---
openapi: 3.0.3
x-snyk-api-stability: beta
info:
title: Registry
version: 3.0.0
servers:
- url: /api/v3
description: Snyk Registry
paths:
/examples/hello-world:
post:
description: Create a single result from the hello-world example - from example 2
operationId: helloWorldCreate
requestBody:
content:
application/vnd.api+json:
schema:
type: object
properties:
attributes:
type: object
properties:
message:
type: string
betaField:
type: string
additionalProperties: false
required: ['message', 'betaField']
additionalProperties: false
required: ['attributes']
responses:
'201':
description: 'A hello world entity being requested is returned'
content:
application/vnd.api+json:
schema:
type: object
required: ['jsonapi', 'data', 'links']
additionalProperties: false
put:
description: Force create a hello-world example - from example 2
operationId: helloWorldForceCreate
requestBody:
content:
application/vnd.api+json:
schema:
type: object
properties:
attributes:
type: object
properties:
message:
type: string
betaField:
type: string
additionalProperties: false
required: ['message', 'betaField']
additionalProperties: false
required: ['attributes']
responses:
'201':
description: 'A hello world entity being requested is returned'
content:
application/vnd.api+json:
schema:
type: object
required: ['jsonapi', 'data', 'links']
additionalProperties: false

0 comments on commit 7de0754

Please sign in to comment.