Skip to content

Commit

Permalink
(pkg/openapi3) Basic support of primitive types
Browse files Browse the repository at this point in the history
  • Loading branch information
azzz committed Oct 1, 2020
1 parent 3e092f7 commit 8be015b
Show file tree
Hide file tree
Showing 50 changed files with 5,772 additions and 0 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require (
github.com/agext/levenshtein v1.2.3 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/francoispqt/gojay v1.2.13
github.com/getkin/kin-openapi v0.22.1
github.com/go-test/deep v1.0.7
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d // indirect
github.com/golang/protobuf v1.4.1
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiD
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsouza/go-dockerclient v1.6.0/go.mod h1:YWwtNPuL4XTX1SKJQk86cWPmmqwx+4np9qfPbb+znGc=
github.com/getkin/kin-openapi v0.22.1 h1:ODA1olTp175o//NfHko/uCAAhwUSfm5P4+K52XvTg4w=
github.com/getkin/kin-openapi v0.22.1/go.mod h1:WGRs2ZMM1Q8LR1QBEwUxC6RJEfaBcD0s+pcEVXFuAjw=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
Expand Down Expand Up @@ -510,6 +512,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog=
Expand Down
158 changes: 158 additions & 0 deletions pkg/providers/openapi3/resolver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package openapi3

import (
"fmt"

openapi "github.com/getkin/kin-openapi/openapi3"
"github.com/jexia/semaphore/pkg/broker"
"github.com/jexia/semaphore/pkg/broker/logger"
"github.com/jexia/semaphore/pkg/providers"
"github.com/jexia/semaphore/pkg/specs"
"go.uber.org/zap"
)

// XPackageExtensionField is the name of an info property which is used to define package name.
// Example:
//
// info:
// x-package: com.semaphore
// components:
// <the rest of file>
const XPackageExtensionField = "x-package"

// a dictionary of file names to swagger documents.
type swaggers map[string]*openapi.Swagger

// collect all the swagger documents from the paths.
// imports is a collection of all the files. The file path might include a mask.
// Example: []string{"/etc/schemas/user.yml", "/etc/schemas/animal_*.yml"} and so on.
//
// Pay attention the external references are not supported.
// External references are references from one file to another. Example:
// User:
// $ref: user.yaml#/components/schemas/User
//
// We don't allow them yet as it's too complicated to handle some specific cases.
// Example of a such case:
//
// components.yml file:
//
// components:
// schemas:
// User:
// $ref: "user.yaml#/components/schemas/User"
// Address:
//
// users.yml file:
//
// components:
// schemas:
// User:
// type: "object"
// properties:
// id:
// type: "integer"
//
// when user imports whole folder contains both files (/path/to/folder/*.yml), we face with both User schemas defined
// in different files. Theoretically, we can build dependencies tree and handle such cases, but not for now.
func collect(ctx *broker.Context, imports []string) (swaggers, error) {
var (
docs = swaggers{} // used to collect all the loaded & parsed swagger files
)

loader := openapi.NewSwaggerLoader()
loader.IsExternalRefsAllowed = false

for _, path := range imports {
files, err := providers.ResolvePath(ctx, []string{}, path)
if err != nil {
return nil, fmt.Errorf("failed to resolve path %s: %w", path, err)
}

// iterate over all the files matched by the single import path
for _, file := range files {
doc, err := loader.LoadSwaggerFromFile(file.Path)

if err != nil {
return nil, fmt.Errorf("failed to parse openapi file %s: %w", file.Path, err)
}

docs[file.Path] = doc
}
}

if err := findSchemaConflicts(docs); err != nil {
return nil, fmt.Errorf("name conflict: %w", err)
}

return docs, nil
}

// find duplicated schema names in the collection of swagger documents
func findSchemaConflicts(docs swaggers) error {
var (
found = map[string]string{} // used to collect all the found schemas with file path where the schema was met first time
)

for fileName, doc := range docs {
for schema, _ := range doc.Components.Schemas {
canonicalName := getCanonicalName(doc, schema)
foundIn, ok := found[canonicalName]

if ok {
return fmt.Errorf("`%s` declared in both `%s` and `%s` files", schema, fileName, foundIn)
}

found[canonicalName] = fileName
}
}

return nil
}

// return canonical schema name: package (extracting from info.x-package) and schema name.
//
// Example:
//
// info:
// x-package: com.semaphore
// components:
// schemas:
// User:
// ....
//
// the canonical name for User is superServer.User.
// If the package was not defined, the name would be just `User`.
func getCanonicalName(doc *openapi.Swagger, name string) string {
if doc.Info == nil {
return name
}

xpackage := doc.Info.Extensions[XPackageExtensionField]

if xpackage == "" || xpackage == nil {
return name
}

return fmt.Sprintf("%s.%s", xpackage, name)
}

// SchemaResolver returns a new schema resolver for the given openapi collection
func SchemaResolver(paths []string) providers.SchemaResolver {
return func(ctx *broker.Context) (specs.Schemas, error) {
logger.Debug(ctx, "resolving openapi schemas", zap.Strings("paths", paths))

docs, err := collect(ctx, paths)
if err != nil {
return nil, err
}

schemas, err := newSchema(docs)

if err != nil {
return nil, fmt.Errorf("failed to build schema: %w", err)
}

return schemas, nil
}
}
Loading

0 comments on commit 8be015b

Please sign in to comment.