Skip to content

Commit

Permalink
Add OpenAPI 3 externalDocs validation (#497)
Browse files Browse the repository at this point in the history
  • Loading branch information
vasayxtx authored Feb 27, 2022
1 parent 1f72b37 commit 5352767
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 16 deletions.
14 changes: 14 additions & 0 deletions openapi3/external_docs.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package openapi3

import (
"context"
"fmt"
"net/url"

"github.com/getkin/kin-openapi/jsoninfo"
)

Expand All @@ -19,3 +23,13 @@ func (e *ExternalDocs) MarshalJSON() ([]byte, error) {
func (e *ExternalDocs) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, e)
}

func (e *ExternalDocs) Validate(ctx context.Context) error {
if e.URL == "" {
return fmt.Errorf("url is required")
}
if _, err := url.Parse(e.URL); err != nil {
return fmt.Errorf("url is incorrect: %w", err)
}
return nil
}
42 changes: 42 additions & 0 deletions openapi3/external_docs_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package openapi3

import (
"context"
"testing"

"github.com/stretchr/testify/require"
)

func TestExternalDocs_Validate(t *testing.T) {
tests := []struct {
name string
extDocs *ExternalDocs
expectedErr string
}{
{
name: "url is missing",
extDocs: &ExternalDocs{},
expectedErr: "url is required",
},
{
name: "url is incorrect",
extDocs: &ExternalDocs{URL: "ht tps://example.com"},
expectedErr: `url is incorrect: parse "ht tps://example.com": first path segment in URL cannot contain colon`,
},
{
name: "ok",
extDocs: &ExternalDocs{URL: "https://example.com"},
},
}
for i := range tests {
tt := tests[i]
t.Run(tt.name, func(t *testing.T) {
err := tt.extDocs.Validate(context.Background())
if tt.expectedErr != "" {
require.EqualError(t, err, tt.expectedErr)
} else {
require.NoError(t, err)
}
})
}
}
18 changes: 18 additions & 0 deletions openapi3/openapi3.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,5 +101,23 @@ func (value *T) Validate(ctx context.Context) error {
}
}

{
wrap := func(e error) error { return fmt.Errorf("invalid tags: %w", e) }
if v := value.Tags; v != nil {
if err := v.Validate(ctx); err != nil {
return wrap(err)
}
}
}

{
wrap := func(e error) error { return fmt.Errorf("invalid external docs: %w", e) }
if v := value.ExternalDocs; v != nil {
if err := v.Validate(ctx); err != nil {
return wrap(err)
}
}
}

return nil
}
76 changes: 61 additions & 15 deletions openapi3/openapi3_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,9 @@ func spec() *T {
}

func TestValidation(t *testing.T) {
version := `
openapi: 3.0.2
`
info := `
info:
title: "Hello World REST APIs"
Expand Down Expand Up @@ -381,9 +384,17 @@ paths:
200:
description: "Get a single greeting object"
`
spec := `
openapi: 3.0.2
` + info + paths + `
externalDocs := `
externalDocs:
url: https://root-ext-docs.com
`
tags := `
tags:
- name: "pet"
externalDocs:
url: https://tags-ext-docs.com
`
spec := version + info + paths + externalDocs + tags + `
components:
schemas:
GreetingObject:
Expand All @@ -399,23 +410,58 @@ components:
type: string
`

tests := map[string]string{
spec: "",
strings.Replace(spec, `openapi: 3.0.2`, ``, 1): "value of openapi must be a non-empty string",
strings.Replace(spec, `openapi: 3.0.2`, `openapi: ''`, 1): "value of openapi must be a non-empty string",
strings.Replace(spec, info, ``, 1): "invalid info: must be an object",
strings.Replace(spec, paths, ``, 1): "invalid paths: must be an object",
tests := []struct {
name string
spec string
expectedErr string
}{
{
name: "no errors",
spec: spec,
},
{
name: "version is missing",
spec: strings.Replace(spec, version, "", 1),
expectedErr: "value of openapi must be a non-empty string",
},
{
name: "version is empty string",
spec: strings.Replace(spec, version, "openapi: ''", 1),
expectedErr: "value of openapi must be a non-empty string",
},
{
name: "info section is missing",
spec: strings.Replace(spec, info, ``, 1),
expectedErr: "invalid info: must be an object",
},
{
name: "paths section is missing",
spec: strings.Replace(spec, paths, ``, 1),
expectedErr: "invalid paths: must be an object",
},
{
name: "externalDocs section is invalid",
spec: strings.Replace(spec, externalDocs,
strings.ReplaceAll(externalDocs, "url: https://root-ext-docs.com", "url: ''"), 1),
expectedErr: "invalid external docs: url is required",
},
{
name: "tags section is invalid",
spec: strings.Replace(spec, tags,
strings.ReplaceAll(tags, "url: https://tags-ext-docs.com", "url: ''"), 1),
expectedErr: "invalid tags: invalid external docs: url is required",
},
}

for spec, expectedErr := range tests {
t.Run(expectedErr, func(t *testing.T) {
for i := range tests {
tt := tests[i]
t.Run(tt.name, func(t *testing.T) {
doc := &T{}
err := yaml.Unmarshal([]byte(spec), &doc)
err := yaml.Unmarshal([]byte(tt.spec), &doc)
require.NoError(t, err)

err = doc.Validate(context.Background())
if expectedErr != "" {
require.EqualError(t, err, expectedErr)
if tt.expectedErr != "" {
require.EqualError(t, err, tt.expectedErr)
} else {
require.NoError(t, err)
}
Expand Down
6 changes: 6 additions & 0 deletions openapi3/operation.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package openapi3
import (
"context"
"errors"
"fmt"
"strconv"

"github.com/getkin/kin-openapi/jsoninfo"
Expand Down Expand Up @@ -138,5 +139,10 @@ func (value *Operation) Validate(ctx context.Context) error {
} else {
return errors.New("value of responses must be an object")
}
if v := value.ExternalDocs; v != nil {
if err := v.Validate(ctx); err != nil {
return fmt.Errorf("invalid external docs: %w", err)
}
}
return nil
}
6 changes: 6 additions & 0 deletions openapi3/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -729,6 +729,12 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) (err error)
}
}

if v := schema.ExternalDocs; v != nil {
if err = v.Validate(ctx); err != nil {
return fmt.Errorf("invalid external docs: %w", err)
}
}

return
}

Expand Down
25 changes: 24 additions & 1 deletion openapi3/tag.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package openapi3

import "github.com/getkin/kin-openapi/jsoninfo"
import (
"context"
"fmt"

"github.com/getkin/kin-openapi/jsoninfo"
)

// Tags is specified by OpenAPI/Swagger 3.0 standard.
type Tags []*Tag
Expand All @@ -14,6 +19,15 @@ func (tags Tags) Get(name string) *Tag {
return nil
}

func (tags Tags) Validate(ctx context.Context) error {
for _, v := range tags {
if err := v.Validate(ctx); err != nil {
return err
}
}
return nil
}

// Tag is specified by OpenAPI/Swagger 3.0 standard.
type Tag struct {
ExtensionProps
Expand All @@ -29,3 +43,12 @@ func (t *Tag) MarshalJSON() ([]byte, error) {
func (t *Tag) UnmarshalJSON(data []byte) error {
return jsoninfo.UnmarshalStrictStruct(data, t)
}

func (t *Tag) Validate(ctx context.Context) error {
if v := t.ExternalDocs; v != nil {
if err := v.Validate(ctx); err != nil {
return fmt.Errorf("invalid external docs: %w", err)
}
}
return nil
}

0 comments on commit 5352767

Please sign in to comment.