diff --git a/config/config.go b/config/config.go
index f46acce..fa95c65 100644
--- a/config/config.go
+++ b/config/config.go
@@ -21,16 +21,17 @@ type RegexCache interface {
//
// Generally fluent With... style functions are used to establish the desired behavior.
type ValidationOptions struct {
- RegexEngine jsonschema.RegexpEngine
- RegexCache RegexCache // Enable compiled regex caching
- FormatAssertions bool
- ContentAssertions bool
- SecurityValidation bool
- OpenAPIMode bool // Enable OpenAPI-specific vocabulary validation
- AllowScalarCoercion bool // Enable string->boolean/number coercion
- Formats map[string]func(v any) error
- SchemaCache cache.SchemaCache // Optional cache for compiled schemas
- Logger *slog.Logger // Logger for debug/error output (nil = silent)
+ RegexEngine jsonschema.RegexpEngine
+ RegexCache RegexCache // Enable compiled regex caching
+ FormatAssertions bool
+ ContentAssertions bool
+ SecurityValidation bool
+ OpenAPIMode bool // Enable OpenAPI-specific vocabulary validation
+ AllowScalarCoercion bool // Enable string->boolean/number coercion
+ Formats map[string]func(v any) error
+ SchemaCache cache.SchemaCache // Optional cache for compiled schemas
+ Logger *slog.Logger // Logger for debug/error output (nil = silent)
+ AllowXMLBodyValidation bool // Allows to convert XML to JSON when validating a request/response body.
// strict mode options - detect undeclared properties even when additionalProperties: true
StrictMode bool // Enable strict property validation
@@ -75,6 +76,7 @@ func WithExistingOpts(options *ValidationOptions) Option {
o.Formats = options.Formats
o.SchemaCache = options.SchemaCache
o.Logger = options.Logger
+ o.AllowXMLBodyValidation = options.AllowXMLBodyValidation
o.StrictMode = options.StrictMode
o.StrictIgnorePaths = options.StrictIgnorePaths
o.StrictIgnoredHeaders = options.StrictIgnoredHeaders
@@ -161,6 +163,14 @@ func WithScalarCoercion() Option {
}
}
+// WithXmlBodyValidation enables converting an XML body to a JSON when validating the schema from a request and response body
+// The default option is set to false
+func WithXmlBodyValidation() Option {
+ return func(o *ValidationOptions) {
+ o.AllowXMLBodyValidation = true
+ }
+}
+
// WithSchemaCache sets a custom cache implementation or disables caching if nil.
// Pass nil to disable schema caching and skip cache warming during validator initialization.
// The default cache is a thread-safe sync.Map wrapper.
diff --git a/config/config_test.go b/config/config_test.go
index dddd739..40ea92d 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -19,8 +19,9 @@ func TestNewValidationOptions_Defaults(t *testing.T) {
assert.False(t, opts.FormatAssertions)
assert.False(t, opts.ContentAssertions)
assert.True(t, opts.SecurityValidation)
- assert.True(t, opts.OpenAPIMode) // Default is true
- assert.False(t, opts.AllowScalarCoercion) // Default is false
+ assert.True(t, opts.OpenAPIMode) // Default is true
+ assert.False(t, opts.AllowScalarCoercion) // Default is false
+ assert.False(t, opts.AllowXMLBodyValidation) // Default is false
assert.Nil(t, opts.RegexEngine)
assert.Nil(t, opts.RegexCache)
}
@@ -32,8 +33,9 @@ func TestNewValidationOptions_WithNilOption(t *testing.T) {
assert.False(t, opts.FormatAssertions)
assert.False(t, opts.ContentAssertions)
assert.True(t, opts.SecurityValidation)
- assert.True(t, opts.OpenAPIMode) // Default is true
- assert.False(t, opts.AllowScalarCoercion) // Default is false
+ assert.True(t, opts.OpenAPIMode) // Default is true
+ assert.False(t, opts.AllowScalarCoercion) // Default is false
+ assert.False(t, opts.AllowXMLBodyValidation) // Default is false
assert.Nil(t, opts.RegexEngine)
assert.Nil(t, opts.RegexCache)
}
@@ -44,8 +46,9 @@ func TestWithFormatAssertions(t *testing.T) {
assert.True(t, opts.FormatAssertions)
assert.False(t, opts.ContentAssertions)
assert.True(t, opts.SecurityValidation)
- assert.True(t, opts.OpenAPIMode) // Default is true
- assert.False(t, opts.AllowScalarCoercion) // Default is false
+ assert.True(t, opts.OpenAPIMode) // Default is true
+ assert.False(t, opts.AllowScalarCoercion) // Default is false
+ assert.False(t, opts.AllowXMLBodyValidation) // Default is false
assert.Nil(t, opts.RegexEngine)
assert.Nil(t, opts.RegexCache)
}
@@ -56,8 +59,9 @@ func TestWithContentAssertions(t *testing.T) {
assert.False(t, opts.FormatAssertions)
assert.True(t, opts.ContentAssertions)
assert.True(t, opts.SecurityValidation)
- assert.True(t, opts.OpenAPIMode) // Default is true
- assert.False(t, opts.AllowScalarCoercion) // Default is false
+ assert.True(t, opts.OpenAPIMode) // Default is true
+ assert.False(t, opts.AllowScalarCoercion) // Default is false
+ assert.False(t, opts.AllowXMLBodyValidation) // Default is false
assert.Nil(t, opts.RegexEngine)
assert.Nil(t, opts.RegexCache)
}
@@ -93,11 +97,12 @@ func TestWithExistingOpts(t *testing.T) {
// Create original options with all settings enabled
var testEngine jsonschema.RegexpEngine = nil
original := &ValidationOptions{
- RegexEngine: testEngine,
- RegexCache: &sync.Map{},
- FormatAssertions: true,
- ContentAssertions: true,
- SecurityValidation: false,
+ RegexEngine: testEngine,
+ RegexCache: &sync.Map{},
+ FormatAssertions: true,
+ AllowXMLBodyValidation: true,
+ ContentAssertions: true,
+ SecurityValidation: false,
}
// Create new options using existing options
@@ -105,6 +110,7 @@ func TestWithExistingOpts(t *testing.T) {
assert.Nil(t, opts.RegexEngine) // Both should be nil
assert.NotNil(t, opts.RegexCache)
+ assert.Equal(t, original.AllowXMLBodyValidation, opts.AllowXMLBodyValidation)
assert.Equal(t, original.FormatAssertions, opts.FormatAssertions)
assert.Equal(t, original.ContentAssertions, opts.ContentAssertions)
assert.Equal(t, original.SecurityValidation, opts.SecurityValidation)
@@ -119,8 +125,9 @@ func TestWithExistingOpts_NilSource(t *testing.T) {
assert.False(t, opts.FormatAssertions)
assert.False(t, opts.ContentAssertions)
assert.True(t, opts.SecurityValidation)
- assert.True(t, opts.OpenAPIMode) // Default is true
- assert.False(t, opts.AllowScalarCoercion) // Default is false
+ assert.True(t, opts.OpenAPIMode) // Default is true
+ assert.False(t, opts.AllowScalarCoercion) // Default is false
+ assert.False(t, opts.AllowXMLBodyValidation) // Default is false
assert.Nil(t, opts.RegexEngine)
assert.Nil(t, opts.RegexCache)
}
@@ -129,11 +136,13 @@ func TestMultipleOptions(t *testing.T) {
opts := NewValidationOptions(
WithFormatAssertions(),
WithContentAssertions(),
+ WithXmlBodyValidation(),
)
assert.True(t, opts.FormatAssertions)
assert.True(t, opts.ContentAssertions)
assert.True(t, opts.SecurityValidation)
+ assert.True(t, opts.AllowXMLBodyValidation)
assert.True(t, opts.OpenAPIMode) // Default is true
assert.False(t, opts.AllowScalarCoercion) // Default is false
assert.Nil(t, opts.RegexEngine)
diff --git a/requests/validate_body.go b/requests/validate_body.go
index 6e9c13a..b5f004a 100644
--- a/requests/validate_body.go
+++ b/requests/validate_body.go
@@ -4,7 +4,10 @@
package requests
import (
+ "bytes"
+ "encoding/json"
"fmt"
+ "io"
"net/http"
"strings"
@@ -14,6 +17,7 @@ import (
"github.com/pb33f/libopenapi-validator/errors"
"github.com/pb33f/libopenapi-validator/helpers"
"github.com/pb33f/libopenapi-validator/paths"
+ "github.com/pb33f/libopenapi-validator/schema_validation"
)
func (v *requestBodyValidator) ValidateRequestBody(request *http.Request) (bool, []*errors.ValidationError) {
@@ -24,6 +28,22 @@ func (v *requestBodyValidator) ValidateRequestBody(request *http.Request) (bool,
return v.ValidateRequestBodyWithPathItem(request, pathItem, foundPath)
}
+func generateXmlValidationError(err error, referenceObject string) []*errors.ValidationError {
+ return []*errors.ValidationError{{
+ ValidationType: helpers.RequestBodyValidation,
+ ValidationSubType: helpers.Schema,
+ Message: "xml example is malformed",
+ Reason: fmt.Sprintf("failed to parse xml: %s", err.Error()),
+ SchemaValidationErrors: []*errors.SchemaValidationFailure{{
+ Reason: err.Error(),
+ Location: "xml parsing",
+ ReferenceSchema: "",
+ ReferenceObject: referenceObject,
+ }},
+ HowToFix: "ensure xml is well-formed and matches schema structure",
+ }}
+}
+
func (v *requestBodyValidator) ValidateRequestBodyWithPathItem(request *http.Request, pathItem *v3.PathItem, pathValue string) (bool, []*errors.ValidationError) {
if pathItem == nil {
return false, []*errors.ValidationError{{
@@ -66,12 +86,6 @@ func (v *requestBodyValidator) ValidateRequestBodyWithPathItem(request *http.Req
return false, []*errors.ValidationError{errors.RequestContentTypeNotFound(operation, request, pathValue)}
}
- // we currently only support JSON validation for request bodies
- // this will capture *everything* that contains some form of 'json' in the content type
- if !strings.Contains(strings.ToLower(contentType), helpers.JSONType) {
- return true, nil
- }
-
// Nothing to validate
if mediaType.Schema == nil {
return true, nil
@@ -80,6 +94,32 @@ func (v *requestBodyValidator) ValidateRequestBodyWithPathItem(request *http.Req
// extract schema from media type
schema := mediaType.Schema.Schema()
+ if !strings.Contains(strings.ToLower(contentType), helpers.JSONType) {
+ // we currently only support JSON and XML validation for request bodies
+ // this will capture *everything* that contains some form of 'json' in the content type
+ if !v.options.AllowXMLBodyValidation || !schema_validation.IsXMLContentType(contentType) {
+ return true, nil
+ }
+
+ if request != nil && request.Body != nil {
+ requestBody, _ := io.ReadAll(request.Body)
+ _ = request.Body.Close()
+
+ stringedBody := string(requestBody)
+ jsonBody, err := schema_validation.TransformXMLToSchemaJSON(stringedBody, schema)
+ if err != nil {
+ return false, generateXmlValidationError(err, stringedBody)
+ }
+
+ transformedBytes, err := json.Marshal(jsonBody)
+ if err != nil {
+ return false, generateXmlValidationError(err, stringedBody)
+ }
+
+ request.Body = io.NopCloser(bytes.NewBuffer(transformedBytes))
+ }
+ }
+
validationSucceeded, validationErrors := ValidateRequestSchema(&ValidateRequestSchemaInput{
Request: request,
Schema: schema,
diff --git a/requests/validate_body_test.go b/requests/validate_body_test.go
index bc96085..22088db 100644
--- a/requests/validate_body_test.go
+++ b/requests/validate_body_test.go
@@ -16,6 +16,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/pb33f/libopenapi-validator/config"
+ "github.com/pb33f/libopenapi-validator/helpers"
"github.com/pb33f/libopenapi-validator/paths"
)
@@ -1576,3 +1577,121 @@ paths:
assert.True(t, valid)
assert.Len(t, errors, 0)
}
+
+func TestValidateBody_XmlRequest(t *testing.T) {
+ spec := `openapi: 3.1.0
+paths:
+ /burgers/createBurger:
+ post:
+ requestBody:
+ content:
+ application/xml:
+ schema:
+ type: object
+ required:
+ - name
+ properties:
+ name:
+ type: string
+ patties:
+ type: integer
+ xml:
+ name: cost`
+
+ doc, _ := libopenapi.NewDocument([]byte(spec))
+
+ m, _ := doc.BuildV3Model()
+ v := NewRequestBodyValidator(&m.Model, config.WithXmlBodyValidation())
+
+ body := "cheeseburger23"
+
+ request, _ := http.NewRequest(http.MethodPost, "https://things.com/burgers/createBurger",
+ bytes.NewBuffer([]byte(body)))
+ request.Header.Set("Content-Type", "application/xml")
+
+ valid, errors := v.ValidateRequestBody(request)
+
+ assert.True(t, valid)
+ assert.Len(t, errors, 0)
+}
+
+func TestValidateBody_XmlMalformedRequest(t *testing.T) {
+ spec := `openapi: 3.1.0
+paths:
+ /burgers/createBurger:
+ post:
+ requestBody:
+ content:
+ application/xml:
+ schema:
+ type: object
+ required:
+ - name
+ properties:
+ name:
+ type: string
+ patties:
+ type: integer
+ xml:
+ name: cost`
+
+ doc, _ := libopenapi.NewDocument([]byte(spec))
+
+ m, _ := doc.BuildV3Model()
+ v := NewRequestBodyValidator(&m.Model, config.WithXmlBodyValidation())
+
+ body := ""
+
+ request, _ := http.NewRequest(http.MethodPost, "https://things.com/burgers/createBurger",
+ bytes.NewBuffer([]byte(body)))
+ request.Header.Set("Content-Type", "application/xml")
+
+ valid, errors := v.ValidateRequestBody(request)
+
+ assert.False(t, valid)
+ assert.Len(t, errors, 1)
+
+ err := errors[0]
+ assert.Equal(t, helpers.RequestBodyValidation, err.ValidationType)
+ assert.Contains(t, err.Reason, "failed to parse xml")
+}
+
+func TestValidateBody_XmlRequestTransformations(t *testing.T) {
+ spec := `openapi: 3.1.0
+paths:
+ /burgers/createBurger:
+ post:
+ requestBody:
+ content:
+ application/xml:
+ schema:
+ type: object
+ xml:
+ name: Burger
+ required:
+ - name
+ - patties
+ properties:
+ name:
+ type: string
+ patties:
+ type: integer
+ xml:
+ name: cost`
+
+ doc, _ := libopenapi.NewDocument([]byte(spec))
+
+ m, _ := doc.BuildV3Model()
+ v := NewRequestBodyValidator(&m.Model, config.WithXmlBodyValidation())
+
+ body := "cheeseburger23"
+
+ request, _ := http.NewRequest(http.MethodPost, "https://things.com/burgers/createBurger",
+ bytes.NewBuffer([]byte(body)))
+ request.Header.Set("Content-Type", "application/xml")
+
+ valid, errors := v.ValidateRequestBody(request)
+
+ assert.True(t, valid)
+ assert.Len(t, errors, 0)
+}
diff --git a/responses/validate_body.go b/responses/validate_body.go
index 4d532d8..edeb649 100644
--- a/responses/validate_body.go
+++ b/responses/validate_body.go
@@ -4,7 +4,10 @@
package responses
import (
+ "bytes"
+ "encoding/json"
"fmt"
+ "io"
"net/http"
"strconv"
"strings"
@@ -17,6 +20,7 @@ import (
"github.com/pb33f/libopenapi-validator/errors"
"github.com/pb33f/libopenapi-validator/helpers"
"github.com/pb33f/libopenapi-validator/paths"
+ "github.com/pb33f/libopenapi-validator/schema_validation"
)
func (v *responseBodyValidator) ValidateResponseBody(
@@ -123,6 +127,22 @@ func (v *responseBodyValidator) ValidateResponseBodyWithPathItem(request *http.R
return true, nil
}
+func generateXmlValidationError(err error, referenceObject string) []*errors.ValidationError {
+ return []*errors.ValidationError{{
+ ValidationType: helpers.RequestBodyValidation,
+ ValidationSubType: helpers.Schema,
+ Message: "xml response is malformed",
+ Reason: fmt.Sprintf("failed to parse xml: %s", err.Error()),
+ SchemaValidationErrors: []*errors.SchemaValidationFailure{{
+ Reason: err.Error(),
+ Location: "xml parsing",
+ ReferenceSchema: "",
+ ReferenceObject: referenceObject,
+ }},
+ HowToFix: "ensure xml is well-formed and matches schema structure",
+ }}
+}
+
func (v *responseBodyValidator) checkResponseSchema(
request *http.Request,
response *http.Response,
@@ -131,26 +151,54 @@ func (v *responseBodyValidator) checkResponseSchema(
) []*errors.ValidationError {
var validationErrors []*errors.ValidationError
- // currently, we can only validate JSON based responses, so check for the presence
- // of 'json' in the content type (what ever it may be) so we can perform a schema check on it.
- // anything other than JSON, will be ignored.
- if strings.Contains(strings.ToLower(contentType), helpers.JSONType) {
- // extract schema from media type
- if mediaType.Schema != nil {
- schema := mediaType.Schema.Schema()
-
- // Validate response schema
- valid, vErrs := ValidateResponseSchema(&ValidateResponseSchemaInput{
- Request: request,
- Response: response,
- Schema: schema,
- Version: helpers.VersionToFloat(v.document.Version),
- Options: []config.Option{config.WithExistingOpts(v.options)},
- })
- if !valid {
- validationErrors = append(validationErrors, vErrs...)
+ if mediaType.Schema == nil {
+ return validationErrors
+ }
+
+ // currently, we can only validate JSON and XML based responses, so check for the presence
+ // of 'json' (what ever it may be) and for XML content type so we can perform a schema check on it.
+ // anything other than JSON or XML, will be ignored.
+
+ isXml := schema_validation.IsXMLContentType(contentType)
+
+ if !strings.Contains(strings.ToLower(contentType), helpers.JSONType) && (!v.options.AllowXMLBodyValidation || !isXml) {
+ return validationErrors
+ }
+
+ schema := mediaType.Schema.Schema()
+
+ if isXml {
+ if response != nil && response.Body != http.NoBody {
+ responseBody, _ := io.ReadAll(response.Body)
+ _ = response.Body.Close()
+
+ stringedBody := string(responseBody)
+ jsonBody, err := schema_validation.TransformXMLToSchemaJSON(stringedBody, schema)
+ if err != nil {
+ return generateXmlValidationError(err, stringedBody)
+ }
+
+ transformedBytes, err := json.Marshal(jsonBody)
+ if err != nil {
+ return generateXmlValidationError(err, stringedBody)
}
+
+ response.Body = io.NopCloser(bytes.NewBuffer(transformedBytes))
}
}
+
+ // Validate response schema
+ valid, vErrs := ValidateResponseSchema(&ValidateResponseSchemaInput{
+ Request: request,
+ Response: response,
+ Schema: schema,
+ Version: helpers.VersionToFloat(v.document.Version),
+ Options: []config.Option{config.WithExistingOpts(v.options)},
+ })
+
+ if !valid {
+ validationErrors = append(validationErrors, vErrs...)
+ }
+
return validationErrors
}
diff --git a/responses/validate_body_test.go b/responses/validate_body_test.go
index a40bdd1..7815638 100644
--- a/responses/validate_body_test.go
+++ b/responses/validate_body_test.go
@@ -1593,6 +1593,208 @@ paths:
assert.Len(t, errs, 0)
}
+func TestValidateBody_ValidXmlDecode(t *testing.T) {
+ spec := `openapi: 3.1.0
+paths:
+ /burgers/createBurger:
+ post:
+ responses:
+ default:
+ content:
+ application/xml:
+ schema:
+ type: object
+ properties:
+ name:
+ type: string
+ patties:
+ type: integer`
+
+ doc, _ := libopenapi.NewDocument([]byte(spec))
+
+ m, _ := doc.BuildV3Model()
+ v := NewResponseBodyValidator(&m.Model, config.WithXmlBodyValidation())
+
+ body := "test2"
+
+ // build a request
+ request, _ := http.NewRequest(http.MethodPost, "https://things.com/burgers/createBurger", bytes.NewReader([]byte(body)))
+ request.Header.Set(helpers.ContentTypeHeader, helpers.JSONContentType)
+
+ // simulate a request/response
+ res := httptest.NewRecorder()
+ handler := func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set(helpers.ContentTypeHeader, "application/xml")
+ w.WriteHeader(http.StatusOK)
+ _, _ = w.Write([]byte(body))
+ }
+
+ // fire the request
+ handler(res, request)
+
+ // record response
+ response := res.Result()
+
+ // validate!
+ valid, errors := v.ValidateResponseBody(request, response)
+
+ assert.True(t, valid)
+ assert.Len(t, errors, 0)
+}
+
+func TestValidateBody_ValidXmlFailedValidation(t *testing.T) {
+ spec := `openapi: 3.1.0
+paths:
+ /burgers/createBurger:
+ post:
+ responses:
+ default:
+ content:
+ application/xml:
+ schema:
+ type: object
+ properties:
+ name:
+ type: string
+ patties:
+ type: integer`
+
+ doc, _ := libopenapi.NewDocument([]byte(spec))
+
+ m, _ := doc.BuildV3Model()
+ v := NewResponseBodyValidator(&m.Model, config.WithXmlBodyValidation())
+
+ body := "20text"
+
+ // build a request
+ request, _ := http.NewRequest(http.MethodPost, "https://things.com/burgers/createBurger", bytes.NewReader([]byte(body)))
+ request.Header.Set(helpers.ContentTypeHeader, helpers.JSONContentType)
+
+ // simulate a request/response
+ res := httptest.NewRecorder()
+ handler := func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set(helpers.ContentTypeHeader, "application/xml")
+ w.WriteHeader(http.StatusOK)
+ _, _ = w.Write([]byte(body))
+ }
+
+ // fire the request
+ handler(res, request)
+
+ // record response
+ response := res.Result()
+
+ // validate!
+ valid, errors := v.ValidateResponseBody(request, response)
+
+ assert.False(t, valid)
+ assert.Len(t, errors, 1)
+ assert.Len(t, errors[0].SchemaValidationErrors, 2)
+}
+
+func TestValidateBody_IgnoreXmlValidation(t *testing.T) {
+ spec := `openapi: 3.1.0
+paths:
+ /burgers/createBurger:
+ post:
+ responses:
+ default:
+ content:
+ application/xml:
+ schema:
+ type: object
+ properties:
+ name:
+ type: string
+ patties:
+ type: integer
+ vegetarian:
+ type: boolean`
+
+ doc, _ := libopenapi.NewDocument([]byte(spec))
+
+ m, _ := doc.BuildV3Model()
+ v := NewResponseBodyValidator(&m.Model)
+
+ body := "invalidbodycausenoxml"
+
+ // build a request
+ request, _ := http.NewRequest(http.MethodPost, "https://things.com/burgers/createBurger", bytes.NewReader([]byte(body)))
+ request.Header.Set(helpers.ContentTypeHeader, helpers.JSONContentType)
+
+ // simulate a request/response
+ res := httptest.NewRecorder()
+ handler := func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set(helpers.ContentTypeHeader, "application/xml")
+ w.WriteHeader(http.StatusOK)
+ _, _ = w.Write([]byte(body))
+ }
+
+ // fire the request
+ handler(res, request)
+
+ // record response
+ response := res.Result()
+
+ // validate!
+ valid, errors := v.ValidateResponseBody(request, response)
+
+ assert.True(t, valid)
+ assert.Len(t, errors, 0)
+}
+
+func TestValidateBody_InvalidXmlParse(t *testing.T) {
+ spec := `openapi: 3.1.0
+paths:
+ /burgers/createBurger:
+ post:
+ responses:
+ default:
+ content:
+ application/xml:
+ schema:
+ type: object
+ properties:
+ name:
+ type: string
+ patties:
+ type: integer
+ vegetarian:
+ type: boolean`
+
+ doc, _ := libopenapi.NewDocument([]byte(spec))
+
+ m, _ := doc.BuildV3Model()
+ v := NewResponseBodyValidator(&m.Model, config.WithXmlBodyValidation())
+
+ body := ""
+
+ // build a request
+ request, _ := http.NewRequest(http.MethodPost, "https://things.com/burgers/createBurger", bytes.NewReader([]byte(body)))
+ request.Header.Set(helpers.ContentTypeHeader, helpers.JSONContentType)
+
+ // simulate a request/response
+ res := httptest.NewRecorder()
+ handler := func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set(helpers.ContentTypeHeader, "application/xml")
+ w.WriteHeader(http.StatusOK)
+ _, _ = w.Write([]byte(body))
+ }
+
+ // fire the request
+ handler(res, request)
+
+ // record response
+ response := res.Result()
+
+ // validate!
+ valid, errors := v.ValidateResponseBody(request, response)
+
+ assert.False(t, valid)
+ assert.Len(t, errors, 1)
+ assert.Equal(t, errors[0].Message, "xml response is malformed")
+}
+
type errorReader struct{}
func (er *errorReader) Read(p []byte) (n int, err error) {
diff --git a/schema_validation/validate_xml.go b/schema_validation/validate_xml.go
index ad30bd8..518bcd4 100644
--- a/schema_validation/validate_xml.go
+++ b/schema_validation/validate_xml.go
@@ -26,7 +26,7 @@ func (x *xmlValidator) validateXMLWithVersion(schema *base.Schema, xmlString str
}
// parse xml and transform to json structure matching schema
- transformedJSON, err := transformXMLToSchemaJSON(xmlString, schema)
+ transformedJSON, err := TransformXMLToSchemaJSON(xmlString, schema)
if err != nil {
violation := &liberrors.SchemaValidationFailure{
Reason: err.Error(),
@@ -49,9 +49,9 @@ func (x *xmlValidator) validateXMLWithVersion(schema *base.Schema, xmlString str
return x.schemaValidator.validateSchemaWithVersion(schema, nil, transformedJSON, log, version)
}
-// transformXMLToSchemaJSON converts xml to json structure matching openapi schema.
+// TransformXMLToSchemaJSON converts xml to json structure matching openapi schema.
// applies xml object transformations: name, attribute, wrapped.
-func transformXMLToSchemaJSON(xmlString string, schema *base.Schema) (interface{}, error) {
+func TransformXMLToSchemaJSON(xmlString string, schema *base.Schema) (interface{}, error) {
if xmlString == "" {
return nil, fmt.Errorf("empty xml content")
}
diff --git a/schema_validation/validate_xml_test.go b/schema_validation/validate_xml_test.go
index 1d3faad..692e32b 100644
--- a/schema_validation/validate_xml_test.go
+++ b/schema_validation/validate_xml_test.go
@@ -960,7 +960,7 @@ func TestValidateXML_UnwrapArrayElementMissingItem(t *testing.T) {
func TestTransformXMLToSchemaJSON_EmptyString(t *testing.T) {
// test empty string error path (line 68)
schema := &base.Schema{}
- _, err := transformXMLToSchemaJSON("", schema)
+ _, err := TransformXMLToSchemaJSON("", schema)
assert.Error(t, err)
assert.Contains(t, err.Error(), "empty xml")
}