-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
(pkg/openapi3) Basic support of primitive types
- Loading branch information
Showing
50 changed files
with
5,772 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
Oops, something went wrong.