diff --git a/README.md b/README.md index 35102e0..e416418 100644 --- a/README.md +++ b/README.md @@ -31,8 +31,12 @@ go get github.com/pb33f/libopenapi-validator ## Validate OpenAPI Document ```bash -go run github.com/pb33f/libopenapi-validator/cmd/validate@latest [--regexengine] +go run github.com/pb33f/libopenapi-validator/cmd/validate@latest [--regexengine] [--yaml2json] ``` + +### Options + +#### --regexengine 🔍 Example: Use a custom regex engine/flag (e.g., ecmascript) ```bash go run github.com/pb33f/libopenapi-validator/cmd/validate@latest --regexengine=ecmascript @@ -51,6 +55,26 @@ go run github.com/pb33f/libopenapi-validator/cmd/validate@latest --regexengine=e - re2 - unicode +#### --yaml2json +🔍 Convert YAML files to JSON before validation (â„šī¸ Default: false) + +[libopenapi](https://github.com/pb33f/libopenapi/blob/main/datamodel/spec_info.go#L115) passes `map[interface{}]interface{}` structures for deeply nested objects or complex mappings in the OpenAPI specification, which are not allowed in JSON. +These structures cannot be properly converted to JSON by libopenapi and cannot be validated by jsonschema, resulting in ambiguous errors. + +This flag allows pre-converting from YAML to JSON to bypass this limitation of the libopenapi. + +**When does this happen?** +- OpenAPI specs with deeply nested schema definitions +- Complex `allOf`, `oneOf`, or `anyOf` structures with multiple levels +- Specifications with intricate object mappings in examples or schema properties + +Enabling this flag pre-converts the YAML document from YAML to JSON, ensuring a clean JSON structure before validation. + +Example: +```bash +go run github.com/pb33f/libopenapi-validator/cmd/validate@latest --yaml2json +``` + ## Documentation - [The structure of the validator](https://pb33f.io/libopenapi/validation/#the-structure-of-the-validator) diff --git a/cmd/validate/main.go b/cmd/validate/main.go index 58ffcf4..ec1c626 100644 --- a/cmd/validate/main.go +++ b/cmd/validate/main.go @@ -8,6 +8,7 @@ import ( "os" "github.com/dlclark/regexp2" + "github.com/goccy/go-yaml" "github.com/pb33f/libopenapi" "github.com/santhosh-tekuri/jsonschema/v6" @@ -64,13 +65,19 @@ var ( If not specified, the default libopenapi option is "re2". If not specified, the default libopenapi regex engine is "re2"".`) + convertYAMLToJSON = flag.Bool("yaml2json", false, `Convert YAML files to JSON before validation. + libopenapi passes map[interface{}]interface{} structures for deeply nested objects + or complex mappings, which are not allowed in JSON and cannot be validated by jsonschema. + This flag allows pre-converting from YAML to JSON to bypass this limitation of the libopenapi. + Default is false.`) ) // main is the entry point for validating an OpenAPI Specification (OAS) document. // It uses the libopenapi-validator library to check if the provided OAS document // conforms to the OpenAPI specification. // -// This tool accepts a single input file (YAML or JSON) and provides an optional +// This tool accepts a single input file (YAML or JSON) and provides optional flags: +// // `--regexengine` flag to customize the regex engine used during validation. // This is useful for cases where the spec uses regex patterns that require engines // like ECMAScript or RE2. @@ -80,9 +87,16 @@ If not specified, the default libopenapi regex engine is "re2"".`) // - Flags: ignorecase, multiline, explicitcapture, compiled, singleline, // ignorepatternwhitespace, righttoleft, debug, unicode // +// `--yaml2json` flag to convert YAML files to JSON before validation. +// libopenapi passes map[interface{}]interface{} structures for deeply nested +// objects or complex mappings, which are not allowed in JSON and cannot be +// validated by jsonschema. This flag allows pre-converting from YAML to JSON +// to bypass this limitation of the libopenapi. Default is false. +// // Example usage: // // go run main.go --regexengine=ecmascript ./my-api-spec.yaml +// go run main.go --yaml2json ./my-api-spec.yaml // // If validation passes, the tool logs a success message. // If the document is invalid or there is a processing error, it logs details and exits non-zero. @@ -94,13 +108,21 @@ Validates an OpenAPI document using libopenapi-validator. Options: --regexengine string Specify the regex parsing option to use. - Supported values are: + Supported values are: Engines: re2 (default), ecmascript - Flags: ignorecase, multiline, explicitcapture, compiled, - singleline, ignorepatternwhitespace, righttoleft, + Flags: ignorecase, multiline, explicitcapture, compiled, + singleline, ignorepatternwhitespace, righttoleft, debug, unicode If not specified, the default libopenapi option is "re2". + --yaml2json Convert YAML files to JSON before validation. + libopenapi passes map[interface{}]interface{} + structures for deeply nested objects or complex mappings, which + are not allowed in JSON and cannot be validated by jsonschema. + This flag allows pre-converting from YAML to JSON to bypass this + limitation of the libopenapi. + (default: false) + -h, --help Show this help message and exit. `) } @@ -156,6 +178,17 @@ Options: os.Exit(1) } + if *convertYAMLToJSON { + var v interface{} + if err := yaml.Unmarshal(data, &v); err == nil { + data, err = yaml.YAMLToJSON(data) + if err != nil { + logger.Error("invalid api spec: error converting yaml to json", slog.Any("error", err)) + os.Exit(1) + } + } + } + doc, err := libopenapi.NewDocument(data) if err != nil { logger.Error("error creating new libopenapi document", slog.Any("error", err)) diff --git a/go.mod b/go.mod index 3936942..2a3d875 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,9 @@ module github.com/pb33f/libopenapi-validator go 1.24.7 require ( + github.com/basgys/goxml2json v1.1.1-0.20231018121955-e66ee54ceaad github.com/dlclark/regexp2 v1.11.5 + github.com/goccy/go-yaml v1.18.0 github.com/pb33f/jsonpath v0.1.2 github.com/pb33f/libopenapi v0.28.1 github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 @@ -14,7 +16,6 @@ require ( require ( github.com/bahlo/generic-list-go v0.2.0 // indirect - github.com/basgys/goxml2json v1.1.1-0.20231018121955-e66ee54ceaad // indirect github.com/buger/jsonparser v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/pb33f/ordered-map/v2 v2.3.0 // indirect diff --git a/go.sum b/go.sum index f304134..95f9565 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,8 @@ github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= -github.com/basgys/goxml2json v1.1.0 h1:4ln5i4rseYfXNd86lGEB+Vi652IsIXIvggKM/BhUKVw= -github.com/basgys/goxml2json v1.1.0/go.mod h1:wH7a5Np/Q4QoECFIU8zTQlZwZkrilY0itPfecMw41Dw= github.com/basgys/goxml2json v1.1.1-0.20231018121955-e66ee54ceaad h1:3swAvbzgfaI6nKuDDU7BiKfZRdF+h2ZwKgMHd8Ha4t8= github.com/basgys/goxml2json v1.1.1-0.20231018121955-e66ee54ceaad/go.mod h1:9+nBLYNWkvPcq9ep0owWUsPTLgL9ZXTsZWcCSVGGLJ0= +github.com/bitly/go-simplejson v0.5.1 h1:xgwPbetQScXt1gh9BmoJ6j9JMr3TElvuIyjR8pgdoow= github.com/bitly/go-simplejson v0.5.1/go.mod h1:YOPVLzCfwK14b4Sff3oP1AmGhI9T9Vsg84etUnlyp+Q= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= @@ -12,6 +11,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=