Skip to content

Commit

Permalink
Added general linting support for non openapi files.
Browse files Browse the repository at this point in the history
This allows vacuum to operate outside of the OpenAPI world. Exciting!

Signed-off-by: Dave Shanley <[email protected]>
  • Loading branch information
daveshanley committed Jul 22, 2023
1 parent c638860 commit 40e3a24
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 59 deletions.
4 changes: 4 additions & 0 deletions datamodel/document_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ type DocumentConfiguration struct {
// AvoidIndexBuild will avoid building the index. This is disabled by default, only use if you are sure you don't need it.
// This is useful for developers building out models that should be indexed later on.
AvoidIndexBuild bool

// BypassDocumentCheck will bypass the document check. This is disabled by default. This will allow any document to
// passed in and used. Only enable this when parsing non openapi documents.
BypassDocumentCheck bool
}

func NewOpenDocumentConfiguration() *DocumentConfiguration {
Expand Down
138 changes: 81 additions & 57 deletions datamodel/spec_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,11 @@ func (si SpecInfo) GetJSONParsingChannel() chan bool {
return si.JsonParsingChannel
}

// ExtractSpecInfo accepts an OpenAPI/Swagger specification that has been read into a byte array
// and will return a SpecInfo pointer, which contains details on the version and an un-marshaled
// *yaml.Node root node tree. The root node tree is what's used by the library when building out models.
//
// If the spec cannot be parsed correctly then an error will be returned, otherwise the error is nil.
func ExtractSpecInfo(spec []byte) (*SpecInfo, error) {
func ExtractSpecInfoWithConfig(spec []byte, config *DocumentConfiguration) (*SpecInfo, error) {
return ExtractSpecInfoWithDocumentCheck(spec, config.BypassDocumentCheck)
}

func ExtractSpecInfoWithDocumentCheck(spec []byte, bypass bool) (*SpecInfo, error) {

var parsedSpec yaml.Node

Expand Down Expand Up @@ -111,77 +110,102 @@ func ExtractSpecInfo(spec []byte) (*SpecInfo, error) {
close(spec.JsonParsingChannel) // this needs removing at some point
}

// check for specific keys
if openAPI3 != nil {
version, majorVersion, versionError := parseVersionTypeData(openAPI3.Value)
if versionError != nil {
return nil, versionError
}
if !bypass {
// check for specific keys
if openAPI3 != nil {
version, majorVersion, versionError := parseVersionTypeData(openAPI3.Value)
if versionError != nil {
return nil, versionError
}

specVersion.SpecType = utils.OpenApi3
specVersion.Version = version
specVersion.SpecFormat = OAS3
specVersion.SpecType = utils.OpenApi3
specVersion.Version = version
specVersion.SpecFormat = OAS3

// parse JSON
parseJSON(spec, specVersion, &parsedSpec)
// parse JSON
parseJSON(spec, specVersion, &parsedSpec)

// double check for the right version, people mix this up.
if majorVersion < 3 {
specVersion.Error = errors.New("spec is defined as an openapi spec, but is using a swagger (2.0), or unknown version")
return specVersion, specVersion.Error
// double check for the right version, people mix this up.
if majorVersion < 3 {
specVersion.Error = errors.New("spec is defined as an openapi spec, but is using a swagger (2.0), or unknown version")
return specVersion, specVersion.Error
}
}
}

if openAPI2 != nil {
version, majorVersion, versionError := parseVersionTypeData(openAPI2.Value)
if versionError != nil {
return nil, versionError
}
if openAPI2 != nil {
version, majorVersion, versionError := parseVersionTypeData(openAPI2.Value)
if versionError != nil {
return nil, versionError
}

specVersion.SpecType = utils.OpenApi2
specVersion.Version = version
specVersion.SpecFormat = OAS2
specVersion.SpecType = utils.OpenApi2
specVersion.Version = version
specVersion.SpecFormat = OAS2

// parse JSON
parseJSON(spec, specVersion, &parsedSpec)
// parse JSON
parseJSON(spec, specVersion, &parsedSpec)

// I am not certain this edge-case is very frequent, but let's make sure we handle it anyway.
if majorVersion > 2 {
specVersion.Error = errors.New("spec is defined as a swagger (openapi 2.0) spec, but is an openapi 3 or unknown version")
return specVersion, specVersion.Error
}
}
if asyncAPI != nil {
version, majorVersion, versionErr := parseVersionTypeData(asyncAPI.Value)
if versionErr != nil {
return nil, versionErr
// I am not certain this edge-case is very frequent, but let's make sure we handle it anyway.
if majorVersion > 2 {
specVersion.Error = errors.New("spec is defined as a swagger (openapi 2.0) spec, but is an openapi 3 or unknown version")
return specVersion, specVersion.Error
}
}
if asyncAPI != nil {
version, majorVersion, versionErr := parseVersionTypeData(asyncAPI.Value)
if versionErr != nil {
return nil, versionErr
}

specVersion.SpecType = utils.AsyncApi
specVersion.Version = version
// TODO: format for AsyncAPI.
specVersion.SpecType = utils.AsyncApi
specVersion.Version = version
// TODO: format for AsyncAPI.

// parse JSON
parseJSON(spec, specVersion, &parsedSpec)
// parse JSON
parseJSON(spec, specVersion, &parsedSpec)

// so far there is only 2 as a major release of AsyncAPI
if majorVersion > 2 {
specVersion.Error = errors.New("spec is defined as asyncapi, but has a major version that is invalid")
return specVersion, specVersion.Error
// so far there is only 2 as a major release of AsyncAPI
if majorVersion > 2 {
specVersion.Error = errors.New("spec is defined as asyncapi, but has a major version that is invalid")
return specVersion, specVersion.Error
}
}
}

if specVersion.SpecType == "" {
// parse JSON
parseJSON(spec, specVersion, &parsedSpec)
specVersion.Error = errors.New("spec type not supported by libopenapi, sorry")
return specVersion, specVersion.Error
if specVersion.SpecType == "" {
// parse JSON
parseJSON(spec, specVersion, &parsedSpec)
specVersion.Error = errors.New("spec type not supported by libopenapi, sorry")
return specVersion, specVersion.Error
}
} else {
var jsonSpec map[string]interface{}
if utils.IsYAML(string(spec)) {
_ = parsedSpec.Decode(&jsonSpec)
b, _ := json.Marshal(&jsonSpec)
specVersion.SpecJSONBytes = &b
specVersion.SpecJSON = &jsonSpec
} else {
_ = json.Unmarshal(spec, &jsonSpec)
specVersion.SpecJSONBytes = &spec
specVersion.SpecJSON = &jsonSpec

Check warning on line 190 in datamodel/spec_info.go

View check run for this annotation

Codecov / codecov/patch

datamodel/spec_info.go#L188-L190

Added lines #L188 - L190 were not covered by tests
}
close(specVersion.JsonParsingChannel) // this needs removing at some point
}

// detect the original whitespace indentation
specVersion.OriginalIndentation = utils.DetermineWhitespaceLength(string(spec))

return specVersion, nil

}

// ExtractSpecInfo accepts an OpenAPI/Swagger specification that has been read into a byte array
// and will return a SpecInfo pointer, which contains details on the version and an un-marshaled
// *yaml.Node root node tree. The root node tree is what's used by the library when building out models.
//
// If the spec cannot be parsed correctly then an error will be returned, otherwise the error is nil.
func ExtractSpecInfo(spec []byte) (*SpecInfo, error) {
return ExtractSpecInfoWithDocumentCheck(spec, false)
}

// extract version number from specification
Expand Down
34 changes: 34 additions & 0 deletions datamodel/spec_info_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,40 @@ func TestExtractSpecInfo_OpenAPI31(t *testing.T) {
assert.Contains(t, r.APISchema, "https://spec.openapis.org/oas/3.1/schema/2022-10-07")
}

func TestExtractSpecInfo_AnyDocument(t *testing.T) {

random := `something: yeah
nothing:
- one
- two
why:
yes: no`

r, e := ExtractSpecInfoWithDocumentCheck([]byte(random), true)
assert.Nil(t, e)
assert.NotNil(t, r.RootNode)
assert.Equal(t, "something", r.RootNode.Content[0].Content[0].Value)
assert.Len(t, *r.SpecBytes, 55)
}

func TestExtractSpecInfo_AnyDocumentFromConfig(t *testing.T) {

random := `something: yeah
nothing:
- one
- two
why:
yes: no`

r, e := ExtractSpecInfoWithConfig([]byte(random), &DocumentConfiguration{
BypassDocumentCheck: true,
})
assert.Nil(t, e)
assert.NotNil(t, r.RootNode)
assert.Equal(t, "something", r.RootNode.Content[0].Content[0].Value)
assert.Len(t, *r.SpecBytes, 55)
}

func TestExtractSpecInfo_OpenAPIFalse(t *testing.T) {

spec, e := ExtractSpecInfo([]byte(OpenApiFalse))
Expand Down
15 changes: 13 additions & 2 deletions document.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,11 @@ type DocumentModel[T v2high.Swagger | v3high.Document] struct {
// then you can use the NewDocumentWithConfiguration() function instead, which allows you to set a configuration that
// will allow you to control if file or remote references are allowed.
func NewDocument(specByteArray []byte) (Document, error) {
info, err := datamodel.ExtractSpecInfo(specByteArray)
return NewDocumentWithTypeCheck(specByteArray, false)
}

func NewDocumentWithTypeCheck(specByteArray []byte, bypassCheck bool) (Document, error) {
info, err := datamodel.ExtractSpecInfoWithDocumentCheck(specByteArray, bypassCheck)
if err != nil {
return nil, err
}
Expand All @@ -125,7 +129,14 @@ func NewDocument(specByteArray []byte) (Document, error) {
// NewDocumentWithConfiguration is the same as NewDocument, except it's a convenience function that calls NewDocument
// under the hood and then calls SetConfiguration() on the returned Document.
func NewDocumentWithConfiguration(specByteArray []byte, configuration *datamodel.DocumentConfiguration) (Document, error) {
d, err := NewDocument(specByteArray)
var d Document
var err error
if configuration != nil && configuration.BypassDocumentCheck {
d, err = NewDocumentWithTypeCheck(specByteArray, true)

Check warning on line 135 in document.go

View check run for this annotation

Codecov / codecov/patch

document.go#L135

Added line #L135 was not covered by tests
} else {
d, err = NewDocument(specByteArray)
}

if d != nil {
d.SetConfiguration(configuration)
}
Expand Down

0 comments on commit 40e3a24

Please sign in to comment.